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

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 
#include 

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 weight(num + 1, 0);
        vector value(num + 1, 0);
        for (int i = 1; i <= num; i++)
            cin >> weight[i] >> value[i];
        cout << " *********** " << endl;
        vector > f(num + 1, vector(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 
#include 

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 wei(kind + 1, 0);
        vector val(kind + 1, 0);
        vector amount(kind + 1, 0);
        for (int i = 1; i <= kind; i++)
            cin >> val[i] >> wei[i] >> amount[i];
        cout << "***************" << endl;

        vector weight(kind + 1, 0);
        vector value(kind + 1, 0);

        for (int i = 1; i < weight.size(); i++)
        {
            weight[i] = val[i];
            value[i] =  wei[i];
        }

        vector > f(kind + 1, vector(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 
#include 

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 volumn(num + 1);
        vector value(num + 1);
        for (int i = 1; i <= num; i++)
            cin >> volumn[i] >> value[i];
        vector > f(num + 1, vector(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++动态规划——背包问题)