经典算法|背包问题
简述
背包问题(Knapsack problem)是一种组合优化的NP完全问题。
问题可以描述为:给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高
由于其下又可以从浅入深地进行分类,所以此处也分类讨论。
0-1背包
问题与dp求解
有 件物品和一个容量为 的背包。第 件物品的体积是,价值是。求解将哪些物品装入背包可使价值总和最大.
问题特点: 每种物品仅有一件,可以选择放或不放,用子问题定义状态:即表示前 件物品放入一个容量为 的背包可以获得的最大价值。
注:背包容量和物体容量有可能直接是0
由此,利用动态规划方法,可以得到该问题的 状态转移方程:
即是:
如果放第 个物品到容量是 的背包里,那么其价值为 前 个物品放进容量是的背包的最大价值再加上 物品本身的价值;
如果不放,那么当前最大价值就是前个物品放进容量为 的背包所获得的价值。
上述两种情况,谁的结果最大,当前状态的最大价值就取谁。
为了方便解说,此处以HDOJ2602(Bone Collector)为例进行剖析。Bone Collector|原题地址
如下表所示,W 表示放入的某种品类的骨头的价值,C 表示该骨头的体积,横坐标表示待放入背包的体积:
| (W,C) | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| (1,5) | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 |
| (2,4) | 0 | 0 | 0 | 0 | 2 | 2 | 2 | 2 | 2 | 3 | 3 |
| (3,3) | 0 | 0 | 0 | 3 | 3 | 3 | 3 | 5 | 5 | 5 | 5 |
| (4,2) | 0 | 0 | 4 | 4 | 4 | 7 | 7 | 7 | 7 | 9 | 9 |
| (5,1) | 0 | 5 | 5 | 9 | 9 | 9 | 12 | 12 | 12 | 12 | 14 |
以第二行为例,可知,当背包体积大于该骨头的体积 C=5 时,可以将其放入;
以第6行第6列为例,可知,当前最大价值等于【第5行第4列】的价值+当前骨头的价值;
以此类推……
综上,可以得到如下的C代码:
1 |
|
易知,本方法的时间复杂度是,即骨头种类×背包体积,而空间复杂度也是
从表中也可以看出,有大量的位置是空白的。由此,考虑直接用一维数组来完成对dp的求解~
优化空间复杂度
以第一种骨头(1,5)为例,得到其表:
| (V,C) | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 |
我们知道,在考虑算第二种骨头的时候,可能需要用到第一个骨头的数据,并且值得注意的是,随着我们考虑的骨头越来越多,价值只增不减,从上面的代码也可以看出,最后要的结果只需是 dp[n][m],之前的中间数据是可以不需要的。
因此,可以直接在计算第二种骨头的时候将第一个数组覆盖。
状态方程更改为:
其物理含义是当前背包所剩容量为 的情况下所能获得的最大价值 是不放入物品 和放入物品 能获得的价值中,最大的那一个。前提是我们要事先知道当前情况下不放入物品 的最大价值。这和前面的 中“前 个物品”的物理含义不同,这里是针对所有物品。
之前我们循环的伪代码如下:
1 | for i = 1 to n//所有物品 |
如果我们降维成一维数组,然后 还是从 的话,就会出现:
先在 的时候加过一遍“第二种骨头”,然后在计算 的时候如果 用到了 与有关的刚刚已经加过一次了的量,那么此时的 就并不是在加了一次第一个骨头的情况下再加第二个骨头所得到的最大值,而是第二个骨头加了两次的情况。
同理以此类推,那么就会出现 “第 种骨头” 可以无限加的情况,与01背包问题不符。
因此,我们需要在更新第二个骨头的时候,保留前一个骨头的数据都能用上且不被更新过,所以,最终的解决方法就是, 从。
也就是从后往前遍历,可以保留未被更新过的数组内容。
综上,原代码可以更改为:
1 | int dp[1001]; |
完全背包
问题与dp求解
1 | for i = 1 to n//所有物品 |
参考资料
1.背包九讲



