概述
0-1背包问题是一种经典的动态规划问题,它的基本形式是:有一个背包,容量为 C C C,有 n n n 个物品 i i i,每个物品 i i i 的重量为 w i w_i wi,价值为 v i v_i vi。现在要从这 n n n 个物品中选出若干个放入背包中,使得背包中物品的总重量不超过 C C C,且物品的总价值最大。
解题思路
这个问题可以用动态规划来解决,一般使用一个二维数组 d p dp dp 来存储最大价值。
d p [ i ] [ j ] dp[i][j] dp[i][j] 表示在前 i i i 个物品中选择若干个物品,恰好放入容量为 j j j 的背包中,可以获得的最大价值。
那么对于每个物品,我们可以选择将其放入背包中,也可以选择不放入。
如果选择将第 i i i 个物品放入背包中,则有 d p [ i ] [ j ] = d p [ i − 1 ] [ j − w i ] + v i dp[i][j] = dp[i-1][j-w_i] + v_i dp[i][j]=dp[i−1][j−wi]+vi;
如果选择不放入,则有 d p [ i ] [ j ] = d p [ i − 1 ] [ j ] dp[i][j] = dp[i-1][j] dp[i][j]=dp[i−1][j]。我们需要取这两种情况的较大值作为 d p [ i ] [ j ] dp[i][j] dp[i][j] 的值。
可以得到状态转移方程如下:
dp[i][j]=max(dp[i−1][j−wi]+vi,dp[i−1][j])
最后,最大价值就是 d p [ n ] [ C ] dp[n][C] dp[n][C]。
图表分析
书包重量(公斤) | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|
物品不存在 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
物品1:6公斤、48元 | 0 | 0 | 0 | 0 | 0 | 0 | 48 | 48 | 48 |
物品2:1公斤、7元 | 0 | 7 | 7 | 7 | 7 | 7 | 48 | 48 | 48 |
物品3:5公斤、40元 | 0 | 7 | 7 | 7 | 7 | 40 | 48 | 55 | 55 |
物品4:2公斤、12元 | 0 | 7 | 12 | 19 | 19 | 40 | 48 | 55 | 60 |
物品5:1公斤、8元 | 0 | 8 | 15 | 20 | 27 | 40 | 48 | 56 | 63 |
当物品不存在和书包重量为0的时候,背包价值肯定也为0。
切记:dp[i][j]表示前i个物品、背包承重为j时的最大价值。
前一件物品:只有当背包容量为6的时空才可以装下,所以dp[1][6]=48。由于这是选择的前一件物品,那么在背包容量为7和8时,不会考虑后面的物品,值还是48。
前两件物品:当背包容量为1时,可以装下第二件物品,这时背包价值为7,dp[2][1] = 7。一直到书包承重5公斤时,都只能装下物品2,dp[2][1]到dp[2][5]都是等于7。 书包承重6公斤时,可以装物品1或物品2,两者只能装1个,通过对比物品1和物品2的价值大小,我们选择物品1,即dp[2][6]=48。书包承重7公斤时,可以同时装物品1、物品2,此时dp[2][7]=物品2的价值 + 物品1的价值=55。dp[2][8]也是同样的道理。
前三件物品:背包容量从1到4时,只能装下物品2,即最大价值为7。当背包容量为5时,可以装下物品3,价值为40,当背包容量为6时,只装物品1,背包价值为48。
以此类推,可得到五件物品的情况下,背包能装下且得到的最大价值是多少。
示例代码
#include <iostream>
#include <vector>
#include <algorithm>using namespace std;int knapsack(vector<int>& weights, vector<int>& values, int capacity) {int n = weights.size();vector<vector<int>> dp(n + 1, vector<int>(capacity + 1, 0));for (int i = 1; i <= n; i++) {for (int j = 1; j <= capacity; j++) {if (weights[i - 1] > j) {// 当前物品重量大于背包容量,无法放入,继承上一个最优解dp[i][j] = dp[i - 1][j];} else {// 当前物品可以选择放入或者不放入dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weights[i - 1]] + values[i - 1]);}}}return dp[n][capacity];
}int main() {vector<int> weights = { 6, 1, 5, 2, 1 };vector<int> values = { 48, 7, 40, 12, 8 };int capacity = 8;int result = knapsack(weights, values, capacity);cout << "Max value: " << result << endl;return 0;
}
运行结果如下所示:
解析:
首先定义了函数knapsack,该函数接受三个参数:物品重量的数组weights、物品价值的数组values和背包容量capacity。函数返回值为能够装入背包的最大价值。
函数内部,首先获取物品的数量n,然后定义了一个dp数组来保存中间计算结果。其中,dp[i][j]表示前i个物品,容量为j时,能够获得的最大价值。
接下来使用两层循环,遍历每一个可能的状态。对于每个状态,如果当前物品重量大于背包容量,则无法放入背包中,继承上一个最优解。否则,当前物品可以选择放入或者不放入背包中。这里使用了max函数来求解当前状态下的最大值。最后返回dp[n][capacity]即可。
在main函数中,定义了物品重量、价值和背包容量的数组,并调用knapsack函数求解最大价值,并输出结果。
总结
0-1背包问题是一个经典的动态规划问题,求解的是一个有限容量的背包所能携带的最大价值。其特点是:每种物品只有一个,可以选择装或不装,不能部分装入。
0-1背包问题可以用动态规划来解决。具体思路如下:
-
定义状态:定义 dp[i][j] 表示前 i 个物品放入容量为 j 的背包中所能获得的最大价值。
-
初始化状态:dp[0][j] 和 dp[i][0] 都初始化为 0,因为当没有物品或者背包容量为 0 时,所能获得的最大价值为 0。
-
状态转移方程:当当前物品重量大于当前背包容量时,无法放入,当前最优解为继承上一个最优解;否则,当前物品可以选择放入或者不放入,取决于两种情况下的最大价值。dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weights[i - 1]] + values[i - 1]);
-
返回结果:dp[n][capacity] 表示前 n 个物品放入容量为 capacity 的背包中所能获得的最大价值。
代码实现时,可以使用二维数组 dp[i][j] 来存储状态转移方程的结果,其中 i 表示物品的编号,j 表示背包的容量。初始化时,将 dp[0][j] 和 dp[i][0] 都初始化为 0。最终结果为 dp[n][capacity]。时间复杂度为 O(ncapacity),空间复杂度为 O(ncapacity)。