美文网首页
C++动态规划——背包问题

C++动态规划——背包问题

作者: 俊仔系滑翔机 | 来源:发表于2019-08-12 10:54 被阅读0次

    1、数学建模

    背包问题是一类动态规划的问题,其假设的场景为:有一个容积为b的背包,n个体积分别为ai(i = 1 , 2 , 3, ... , n),价值分别为Ci(i = 1, 2, 3, ... , n)的物品,如何以最大的价值装包。建模为数学问题可以表示为:


    背包问题分为3类:0-1背包问题多重背包问题以及完美背包问题

    2、0-1背包问题

    上述两个公式表示的即为0-1背包问题。xi = {0,1}表示物品只有一件,所以装包的方案只有装包(用1表示)以及不装包(用0表示),0-1背包由此得名。0-1背包问题的解决方案可以采用动态规划以及贪心算法两种方案解决。两者的区别如下所示为

    1. 动态规划中全局最优解一定包含某个局部最优解,但不一定包含前一个局部最优解,因此需要记录之前所有的局部最优解。而贪心算法每一步的最优解一定包含上一步的最优解,因此,上一步的解是不需要保留的。
    2. 如果把所有问题看成一颗树的话,动态规划是自底向上的,从叶子向根,构造子问题的解,对每一个子树的根,求出下面每一个叶子的值,最后得到一颗完整的树,并且最终选择其中的最优值作为自身的值。贪心算法是从根出发,每次向下遍历最优子树即可,这样的话就不需要知道一个节点的所有子树的情况。
    3. 动态规划本质为遍历(穷举)法,可以保证结果是最优的,复杂度较高。贪心算法一般是趋近于最优,复杂度较低。

    本文以动态规划为例,求得背包问题的最优解。背包问题的动态规划的核心思路是在一个二维矩阵中进行遍历搜索。通过比较当前物品是否能够装包(是否超过背包限重),以及在能够装包的前提下其价值是否为最大两个方面进行分析。
    背包问题通常需要增加一行和一列存储空间,一是方便物品序号的对应,二是便于起始背包问题寻优的计算。
    0-1背包问题只需要考虑该物品装包与不装包的即可。在他人博客中找到了一道比较简单题目进行练习,假设物品数量为5,背包的限重为17,题目如下:


    物品情况.png

    根据分析可以写出代码

    #include <iostream>
    #include <vector>
    
    using namespace std;
    
    /*
    假设有n个物体,对应n个价值,背包重量是W,如何装包才能有最大的价值问题。
    */
    
    int getMax(int a, int b)
    {
        return a > b ? a : b;
    }
    
    int main()
    {
        int num, W;
        while (cin >> num >> W)
        {
            vector<int> weight(num + 1, 0);
            vector<int> value(num + 1, 0);
            for (int i = 1; i <= num; i++)
                cin >> weight[i] >> value[i];
            cout << " *********** " << endl;
            vector<vector<int> > f(num + 1, vector<int>(W + 1, 0));
    
            for (int i = 1; i <= num; i++)
            {
                for (int j = W; j >= 1; j--)
                {
                    if (j >= weight[i])
                        f[i][j] = getMax(f[i - 1][j], f[i - 1][j - weight[i]] + value[i]);
                    else
                        f[i][j] = f[i - 1][j];
                }
            }
    
            cout << "***********" << endl;
            cout << f[num][W] << endl;
            
        }
        system("pause");
        return 0;
    }
    

    核心代码为

            for (int i = 1; i <= num; i++)
            {
                for (int j = W; j >= 1; j--)
                {
                    if (j >= weight[i])
                        f[i][j] = getMax(f[i - 1][j], f[i - 1][j - weight[i]] + value[i]);
                    else
                        f[i][j] = f[i - 1][j];
                }
            }
    

    即对每一次产生的结果与上一次的最优作比较,产生该次的最优结果。

    f[i - 1][j - weight[i]]+value[i];    // 模拟装包过程
    f[i - 1][j];                                  // 模拟不装包过程
    

    3、多重背包问题

    多重背包问题是指物品不再是只有一件的问题。而是在规定数目下任意一件都行。多重背包问题讨论的情况的变多。同样在他人博客中找到一道例题进行练习。

    题目描述:
    为了挽救灾区同胞的生命,心系灾区同胞的你准备自己采购一些粮食支援灾区,现在假设你一共有资金n元,而市场有m种大米,每种大米都是袋装产品,其价格不等,并且只能整袋购买。请问:你用有限的资金最多能采购多少公斤粮食呢?
    输入: 输入数据首先包含一个正整数C,表示有C组测试用例,每组测试用例的第一行是两个整数n和m(1<=n<=100, 1<=m<=100),分别表示经费的金额和大米的种类,然后是m行数据,每行包含3个数p,h和c(1<=p<=20,1<=h<=200,1<=c<=20),分别表示每袋的价格、每袋的重量以及对应种类大米的袋数。

    解决方案的代码是在0-1背包核心代码中再加一层循环。作为遍历采用M件物品的所有情况(M = 0,1,2, ... M)。可以得到第i件物品的装包情况一共有M+1种。代码如下:

    #include <iostream>
    #include <vector>
    
    using namespace std;
    
    int getMax(int a, int b)
    {
        return a > b ? a : b;
    }
    
    int main()
    {
        int money;
        int kind;
        while (cin >> money >> kind)
        {
            vector<int> wei(kind + 1, 0);
            vector<int> val(kind + 1, 0);
            vector<int> amount(kind + 1, 0);
            for (int i = 1; i <= kind; i++)
                cin >> val[i] >> wei[i] >> amount[i];
            cout << "***************" << endl;
    
            vector<int> weight(kind + 1, 0);
            vector<int> value(kind + 1, 0);
    
            for (int i = 1; i < weight.size(); i++)
            {
                weight[i] = val[i];
                value[i] =  wei[i];
            }
    
            vector<vector<int> > f(kind + 1, vector<int>(money + 1));
            for (int i = 1; i <= kind; i++)
            {
                for (int j = money; j >= 1; j--)
                {
                    for (int k = 0; k <= amount[i]; k++)
                    {
                        if (j >= k*weight[i])
                            f[i][j] = getMax(f[i][j], f[i - 1][j - k*weight[i]] + k* value[i]);
                    }
                }
            }
            cout << f[kind][money] << endl;
    
        }
        system("pause");
        return 0;
    }
    

    相比于0-1背包,多重背包仅仅在核心代码处多了一层循环。

            for (int i = 1; i <= kind; i++)
            {
                for (int j = money; j >= 1; j--)
                {
                    for (int k = 0; k <= amount[i]; k++)
                    {
                        if (j >= k*weight[i])
                            f[i][j] = getMax(f[i][j], f[i - 1][j - k*weight[i]] + k* value[i]);
                    }
                }
            }
    

    其思路与0-1背包的思路基本一致,但是需要考虑的情况变多。


    4、完全背包问题

    完全背包问题是多重背包问题的再拓展,它不再规定每件物品的数量,可以在不超过限重的情况下,无限制地装入某种物品。因此,对比多重背包问题中考虑的是0M个物品装包的M+1种情况。那么假设背包限重为W的情况下,完全背包问题中考虑的是0floor(W/M)的所有情况。因此,代码可能只需要在最内层的循环中修改循环条件即可。
    同样在网路上找到一道完全背包问题进行练习。

    输入为第一行:两个整数,M(背包容量,M<=200)和N(物品数量,N<=30);
    第2..N+1行:每行二个整数Wi,Ci,表示每个物品的重量和价值。

    解决方案的代码为:

    #include <iostream>
    #include <vector>
    
    using namespace std;
    
    int getMax(int a, int b)
    {
        return a > b ? a : b;
    }
    
    int main()
    {
        int num;        // 物品数量
        int capacity;   // 背包数量
        while (cin >> num >> capacity)
        {
            vector<int> volumn(num + 1);
            vector<int> value(num + 1);
            for (int i = 1; i <= num; i++)
                cin >> volumn[i] >> value[i];
            vector<vector<int> > f(num + 1, vector<int>(capacity + 1, 0));
    
            for (int i = 1; i <= num; i++)
            {
                for (int j = capacity; j >= 1; j--)
                {
                    for (int k = 0; k <= capacity / volumn[i]; k++)
                    {
                        if (j >= k*volumn[i])
                            f[i][j] = getMax(f[i][j], f[i - 1][j - k*volumn[i]] + k*value[i]);
                    }
                }
            }
    
            cout << f[num][capacity] << endl;
        }
        system("pause");
        return 0;
    }
    

    可以看到,完全背包问题的内层循环条件为

    k <= capacity / volumn[i]
    

    其思路与上述的0-1背包问题和多重背包问题完全相似。


    相关文章

      网友评论

          本文标题:C++动态规划——背包问题

          本文链接:https://www.haomeiwen.com/subject/jfryjctx.html