1. 问题描述
完全背包问题是动态规划中的经典问题之一。与01背包问题不同,完全背包问题中每种物品可以选择多次,即物品的数量是无限的。
问题形式:
- 给定一个容量为 [V] 的背包和 [N] 种物品。
- 每种物品有一个重量 [w_i] 和一个价值 [v_i]。
- 目标是从这些物品中选择若干件放入背包,使得背包中物品的总重量不超过 [V],且总价值最大。
2. 动态规划解法
完全背包问题可以通过动态规划来解决,核心思想是状态转移方程。
状态定义:
- 设 [dp[i][j]] 表示前 [i] 种物品放入容量为 [j] 的背包中所能获得的最大价值。
状态转移方程:
[ dp[i][j] = \max(dp[i-1][j], dp[i][j-w_i] + v_i) ]
- [dp[i-1][j]]:不选择第 [i] 种物品。
- [dp[i][j-w_i] + v_i]:选择第 [i] 种物品,并继续考虑是否再次选择。
空间优化:
可以将二维数组优化为一维数组,状态转移方程变为:
[ dp[j] = max(dp[j], dp[j-w_i] + v_i) ]
注意:遍历顺序需要从前往后,以确保每种物品可以被多次选择。
3. 示例
假设背包容量 [V = 10],物品重量 [weights = {2, 3, 4}],物品价值 [values = {3, 4, 5}],则调用 complete_knapsack(10, weights, values)
将返回最大价值 12。
1. 二维数组模板代码
二维数组的实现更直观,适合理解完全背包问题的动态规划过程。
int complete_knapsack_2d(int V, vector<int>& weights, vector<int>& values) {
int n = weights.size();
vector<vector<int>> dp(n + 1, vector<int>(V + 1, 0)); // 初始化二维dp数组
for (int i = 1; i <= n; i++) { // 遍历物品
for (int j = 0; j <= V; j++) { // 遍历背包容量
if (j >= weights[i - 1]) { // 如果当前容量可以放入物品
dp[i][j] = max(dp[i - 1][j], dp[i][j - weights[i - 1]] + values[i - 1]);
} else { // 如果当前容量无法放入物品
dp[i][j] = dp[i - 1][j];
}
}
}
return dp[n][V];
}
特点:
- 使用二维数组 [dp[i][j]],表示前 [i] 种物品放入容量为 [j] 的背包中的最大价值。
- 状态转移方程:[ dp[i][j] = \max(dp[i-1][j], dp[i][j-w_i] + v_i) ]。
- 空间复杂度为 [O(N \times V)],适合初学者理解。
2. 一维数组模板代码
一维数组的实现是二维数组的空间优化版本,效率更高。
int complete_knapsack_1d(int V, vector<int>& weights, vector<int>& values) {
int n = weights.size();
vector<int> dp(V + 1, 0); // 初始化一维dp数组
for (int i = 0; i < n; i++) { // 遍历物品
for (int j = weights[i]; j <= V; j++) { // 遍历背包容量
dp[j] = max(dp[j], dp[j - weights[i]] + values[i]);
}
}
return dp[V];
}
特点:
- 使用一维数组 [dp[j]],表示容量为 [j] 的背包中的最大价值。
- 状态转移方程:[ dp[j] = \max(dp[j], dp[j-w_i] + v_i) ]。
- 空间复杂度为 [O(V)],效率更高。
3. 对比总结
特性 | 二维数组实现 | 一维数组实现 |
---|---|---|
空间复杂度 | [O(N * V)] | [O(V)] |
代码复杂度 | 较高 | 较低 |
适用场景 | 初学者理解动态规划过程 | 实际应用,效率优先 |
状态转移方程 | [dp[i][j] = \max(dp[i-1][j], dp[i][j-w_i] + v_i)] | [dp[j] = \max(dp[j], dp[j-w_i] + v_i)] |