背包问题(Knapsack Problem)是一个经典的组合优化问题,广泛应用于计算机科学、运筹学等领域。其基本形式是:
0/1 背包问题(0/1 Knapsack Problem)
给定一组物品,每个物品有一个重量和一个价值,同时给定一个背包的容量,要求在不超过背包容量的前提下,选择若干物品,使得这些物品的总价值最大。
问题描述:
- 有 ( n ) 件物品,每件物品有一个重量 ( w_i ) 和价值 ( v_i )。
- 背包的容量为 ( W )。
- 目标是从这些物品中选择若干个物品放入背包中,使得背包中的物品总重量不超过背包容量 ( W ),并且物品的总价值最大。
0/1 背包问题的递推公式
定义一个二维数组 ( dp[i][w] ),表示前 ( i ) 个物品,放入容量为 ( w ) 的背包中所能获得的最大价值。
递推关系为:
- 如果第 ( i ) 个物品不放入背包中,则最大价值为 ( dp[i-1][w] )。
- 如果第 ( i ) 个物品放入背包中,则最大价值为 ( dp[i-1][w - w_i] + v_i )(前提是 ( w \geq w_i ))。
因此,递推公式为: [ dp[i][w] = \max(dp[i-1][w], dp[i-1][w-w_i] + v_i) ]
其中:
- ( dp[0][w] = 0 ) 对于所有 ( w ),表示没有物品时,背包价值为 0。
- ( dp[i][0] = 0 ) 对于所有 ( i ),表示背包容量为 0 时,无论有多少物品,最大价值都是 0。
动态规划算法实现
1. 基本动态规划算法
function knapsack(weights, values, capacity) {
const n = weights.length;
const dp = Array(n + 1).fill().map(() => Array(capacity + 1).fill(0));
// 填充 dp 数组
for (let i = 1; i <= n; i++) {
for (let w = 1; w <= capacity; w++) {
if (weights[i - 1] <= w) {
dp[i][w] = Math.max(dp[i - 1][w], dp[i - 1][w - weights[i - 1]] + values[i - 1]);
} else {
dp[i][w] = dp[i - 1][w];
}
}
}
return dp[n][capacity]; // 最大价值
}
// 示例
const weights = [2, 3, 4, 5];
const values = [3, 4, 5, 6];
const capacity = 5;
console.log(knapsack(weights, values, capacity)); // 输出 7
2. 空间优化版本
由于动态规划的状态转移仅依赖于前一行的状态,因此可以将二维数组优化为一维数组,空间复杂度从 ( O(nW) ) 降到 ( O(W) )。
function knapsack(weights, values, capacity) {
const n = weights.length;
const dp = Array(capacity + 1).fill(0);
// 填充 dp 数组
for (let i = 0; i < n; i++) {
for (let w = capacity; w >= weights[i]; w--) {
dp[w] = Math.max(dp[w], dp[w - weights[i]] + values[i]);
}
}
return dp[capacity]; // 最大价值
}
// 示例
const weights = [2, 3, 4, 5];
const values = [3, 4, 5, 6];
const capacity = 5;
console.log(knapsack(weights, values, capacity)); // 输出 7
解释:
-
状态转移: 在每次迭代中,对于每个物品 ( i ) 和每个容量 ( w ),我们有两个选择:
- 不选择当前物品 ( i ),即背包的最大价值为
dp[i-1][w]。 - 选择当前物品 ( i ),此时背包的最大价值为
dp[i-1][w-w_i] + v_i,即从剩余容量中放入物品 ( i ) 后的价值。
我们选择这两者中的最大值。
- 不选择当前物品 ( i ),即背包的最大价值为
-
空间优化: 由于每一行的状态只依赖于上一行的状态,所以我们可以用一个一维数组代替二维数组,逐步更新数组中的每个值。
复杂度分析:
-
时间复杂度:
对于每个物品,我们需要遍历所有的容量,从 1 到 ( W ),因此时间复杂度为 ( O(nW) ),其中 ( n ) 是物品数量,( W ) 是背包的容量。 -
空间复杂度:
基本动态规划算法的空间复杂度为 ( O(nW) ),而空间优化版本的空间复杂度为 ( O(W) ),因为只使用了一个一维数组来存储当前的最大价值。
扩展问题:
- 完全背包问题(Unbounded Knapsack): 允许每个物品可以多次放入背包。此时,状态转移公式会有所不同: [ dp[w] = \max(dp[w], dp[w - w_i] + v_i) ]
- 多重背包问题(Multiple Knapsack): 每个物品有多个可用数量,需要考虑每个物品的数量限制。
结论:
0/1 背包问题是一个经典的动态规划问题,适用于物品选择和优化问题。通过合适的优化方法,可以使其在大数据集下更高效地求解。