背包问题 算法与原理
0-1背包 和 部分背包
关于背包问题,其实可以分为两种类型:0-1背包问题(动态规划) 和 部分背包问题(贪心算法)。
- 0-1背包问题:每件物品或被带走,或被留下,(需要做出0-1选择)。小偷不能只带走某个物品的一部分或带走两次以上同一个物品。
在选择是否要把一个物品加到背包中,必须把该物品加进去的子问题的解与不取该物品的子问题的解进行比较。这种方式形成的问题导致了许多重叠子问题,满足动态规划的特征。
- 部分背包问题:小偷可以只带走某个物品的一部分,不必做出0-1选择。
总是选择每一磅价值 (Vi / Wi) 最大的物品添加进背包中。那么其解决过程是:对每磅价值进行排序,依次从大到小选择添加进背包中。
更通俗点理解,0-1背包问题的一件物品可以想象成是一个金锭;而部分背包问题中的一件物品可以想象成是金粉。
题目描述
假设山洞里共有a, b, c, d, e这5件宝物(不是5种宝物),它们的重量分别是2,2,6,5,4,它们的价值分别是6,3,5,4,6,现在给你个承重为10的背包, 怎么装背包,可以才能带走最多的财富。
有编号分别为a,b,c,d,e的五件物品,它们的重量分别是2,2,6,5,4,它们的价值分别是6,3,5,4,6,现在给你个承重为10的背包,如何让背包里装入的物品具有最大的价值总和?
name | weight | value | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
a | 2 | 6 | 0 | 6 | 6 | 9 | 9 | 12 | 12 | 15 | 15 | 15 |
b | 2 | 3 | 0 | 3 | 3 | 6 | 6 | 9 | 9 | 9 | 10 | 11 |
c | 6 | 5 | 0 | 0 | 0 | 6 | 6 | 6 | 6 | 6 | 10 | 11 |
d | 5 | 4 | 0 | 0 | 0 | 6 | 6 | 6 | 6 | 6 | 10 | 10 |
e | 4 | 6 | 0 | 0 | 0 | 6 | 6 | 6 | 6 | 6 | 6 | 6 |
只要你能通过找规律手工填写出上面这张表就算理解了01背包的动态规划算法。
首先要明确这张表是至底向上,从左到右生成的。
讲解1:
为了叙述方便,用e2单元格表示e行2列的单元格,这个单元格的意义是用来表示只有物品e时,有个承重为2的背包,那么这个背包的最大价值是0,因为e物品的重量是4,背包装不了。
对于d2单元格,表示只有物品e,d时,承重为2的背包,所能装入的最大价值,仍然是0,因为物品e,d都不是这个背包能装的。
同理,c2=0,b2=3,a2=6。
讲解2:
对于承重为8的背包,a8=15,是怎么得出的呢?
根据01背包的状态转换方程,需要考察两个值,一个是f[i-1,j],对于这个例子来说就是b8的值9,另一个是f[i-1, j-Wi]+Pi;
在这里,f[i-1, j]表示我有一个承重为8的背包,当只有物品b,c,d,e四件可选时,这个背包能装入的最大价值。
f[i-1, j-Wi]表示我有一个承重为6的背包(等于当前背包承重减去物品a的重量),当只有物品b,c,d,e四件可选时,这个背包能装入的最大价值。
f[i-1, j-Wi]就是指单元格b6,值为9,Pi指的是a物品的价值,即6。
由于\(f[i-1, j-w_i] + Pi = 9 + 6 = 15\) 大于\(f[i-1, j] = 9\),所以物品a应该放入承重为8的背包。
\(f[i-1, j-w_i] + v_i\) 是选择\(v_i\)这件物品时,能够产生的最大价值,而f[i-1, j-Wi] 表示上一个最优状态,\(j-w_i\)是上一个最优状态时的背包容量。
抽象
0-1背包问题子结构:选择一个给定物品i,则需要 比较(选择 i 的形成的子问题的最优解) 与 (不选择 i 的子问题的最优解)。分成两个子问题,进行选择比较,选择最优的。
0-1背包问题递归过程:设有n个物品,背包的重量为w,C[i][w]为最优解。即:
下面给出伪代码实现:
简约的实现
通过上面的伪代码,我们会发现,实现的过程有点罗嗦,下面给出一个简约点的实现,思路都是一样的。
伪代码:
最优值和最优解的求解过程
最优值:求解整个背包最后的总价值达到最优。求解背包问题的最优值,关键是要弄清楚上面的递推式子。
最优解:当背包价值达到最大时,列出所选取的物品都是那些。
代码的实现过程是这样的:
int remainspace = W;
//输出所选择的物品列表:
for(int i=N; i>=1; i--) {
if (remainspace >= goods[i].weight) {
if ((select[i][remainspace]-select[i-1][remainspace-goods[i].weight]==goods[i].value)) {
cout << "item " << i << " is selected!" << endl;
remainspace = remainspace - goods[i].weight;//如果第i个物品被选择,那么背包剩余容量将减去第i个物品的重量 ;
}
}
}
参考
表格:http://blog.csdn.net/mu399/article/details/7722810
http://blog.csdn.net/crayondeng/article/details/15784093