背包问题已经有很多写得很好的博客,这里的背包问题只是一个个人学习总结,希望能够把问题说得更明白清楚易懂。
贴下背包九讲问题的优秀博客网址:
http://blog.youkuaiyun.com/pi9nc/article/details/8142876
PART1:01背包问题
题目:有N件物品,第i件物品的价值为value[i],重量为weight[i],求解将哪些物品装入背包可使价值总和最大,注意每件物品只可以装一次。
这里我们仍然以一个例子来看这个算法。
有一个能够承载10kg的小推车,商店中有如下物品:
序号 | 物品 | 重量(weight) | 价值(value) |
---|---|---|---|
1 | A | 2 | 6 |
2 | B | 2 | 3 |
3 | C | 5 | 10 |
4 | D | 4 | 7 |
5 | E | 3 | 4 |
那么我们现在研究一下,怎样才能让背包中物品的价值尽可能大。我们必须要有一个正确的思路,这是解题的关键。
设W[i][j]表示在背包可容纳重量j,只装序号不大于i的物品时,背包可装入物品的最大价值,value对应于各个物品的价值,weight对应于各个物品的重量。
算法DP公式:W[i][j] = max{如果装序号为i的物品,如果不装序号为i的物品}
=max{W[i-1][j-weight[i]]+value[i] , W[i-1][j]}
算法很简单,也很容易理解,我们不难发现算法的时间复杂度为O(N*V),空间消耗就是用了个二维数组。这里可以优化的地方就是我们不使用二维数组,使用一维数组来解决这个问题。
那么我们设一维数组为array[N]
算法:我们先对A物品分析,找出只装A物品的情况的array[N];
再添加一个B物品进行分析,我们之前已经把A物品的所有的array值都算出来了,所以可以把包含A、B物品的array[N]算出来;
同理直至到E物品,我们就算出来了结果为array[N]。
那么难点在哪里呢?难点在于每个物品最多只能装一个。
我们对只装A物品进行分析:
序号 | 物品 | 重量(weight) | 价值(value) |
---|---|---|---|
1 | A | 2 | 6 |
<—————————–我们需要将array数组初始化为零——————————>
背包可以承载的重量 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|
可装入的物品的最大价值array[i] | 0 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 |
注意:我们这个表应该从10往1生成,只有这样才能保证A物品最多只装了1个,否则结果是多个,生成的错误的表如下:
背包可以承载的重量 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|
可装入的物品的最大价值array[i] | 0 | 6 | 6 | 12 | 12 | 18 | 18 | 24 | 24 | 30 |
注意到这一点就没有别的什么问题了,现在贴上这道题的c语言代码:
#include <stdio.h>
#include <string.h>
#define N 5 //物品的件数
#define W 10 //背包可容纳的最大重量
#define max(a,b) (a>b?a:b)
int main()
{
int i,j;
int array[W+1];
int weight[] = {2,2,5,4,3},
value[] = {6,3,10,7,4};
memset(array, 0, sizeof(array));
for(i=0;i<N;i++){
for(j=W;j>=weight[i];j--){
array[j]=max(array[j],array[j-weight[i]]+value[i]);
}
}
printf("%d\n",array[W]);
return 0;
}
PART2:完全背包问题
题目:有N种物品,第i件物品的价值为value[i],重量为weight[i],求解将哪些物品装入背包可使价值总和最大,注意每种物品可重复装入。
如果看懂了我们上面的01背包问题,你会发现这个问题很简单,只需要我们对01背包的算法进行一丁点改变即可——就是上面说生成错误表的地方,我们就按照那种“错误”的方式生成即可得到完全背包的答案。代码该边就是在第二个循环的地方j从weight[i]到10算array数组。此处无庸赘述。