在算法的世界里,背包问题是一个经典且极具实用性的问题,而 0 - 1 背包问题更是其中的基础和核心。它就像一把钥匙,能帮助我们解决许多资源分配和选择的实际难题。想象一下,你是一位即将踏上冒险之旅的勇士,面前有一个容量有限的背包,而周围摆放着各种珍贵的物品,每个物品都有自己的价值和重量。你需要在有限的背包容量下,选择合适的物品放入背包,使得背包中物品的总价值最大。这就是 0 - 1 背包问题的生动写照。
0 - 1 背包问题的正式定义是:给定一组物品,每个物品有对应的重量 (w_i) 和价值 (v_i)((i = 1,2,\cdots,n)),以及一个容量为 (C) 的背包。要求从这 (n) 个物品中选择若干个放入背包,每个物品只能选择放入或者不放入(即 0 - 1 选择),使得放入背包的物品总重量不超过背包容量 (C),且总价值最大。
例如,有以下物品:
| 物品编号 | 重量 (w_i) | 价值 (v_i) |
| —— | —— | —— |
| 1 | 2 | 3 |
| 2 | 3 | 4 |
| 3 | 4 | 5 |
| 4 | 5 | 6 |
背包容量 (C = 8),我们需要找出一种选择方案,使得背包内物品总价值最大。
动态规划是解决 0 - 1 背包问题的有效方法。它的核心思想是将大问题分解为小问题,并通过保存小问题的解来避免重复计算,从而提高算法效率。
对于 0 - 1 背包问题,我们定义一个二维数组 (dp[i][j]),其中 (i) 表示考虑前 (i) 个物品,(j) 表示背包的当前容量。(dp[i][j]) 表示在前 (i) 个物品中选择,背包容量为 (j) 时所能获得的最大价值。
状态转移方程是动态规划的关键,它描述了如何从子问题的解推导出原问题的解。对于 0 - 1 背包问题,状态转移方程如下:
当 (j < w_i) 时,即当前背包容量不足以放入第 (i) 个物品,此时 (dp[i][j]=dp[i - 1][j]),表示不选择第 (i) 个物品,最大价值与只考虑前 (i - 1) 个物品时相同。
当 (j\geq w_i) 时,我们有两种选择:
我们取这两种选择中的最大值,即 (dp[i][j]=\max(dp[i - 1][j], dp[i - 1][j - w_i]+v_i))。
当 (i = 0) 或 (j = 0) 时,(dp[i][j]=0)。因为当没有物品可供选择((i = 0))或背包容量为 0((j = 0))时,最大价值必然为 0。
def knapsack(weights, values, capacity):
n = len(weights)
# 创建二维数组 dp
dp = [[0 for _ in range(capacity + 1)] for _ in range(n + 1)]
# 填充 dp 数组
for i in range(1, n + 1):
for j in range(1, capacity + 1):
if j < weights[i - 1]:
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]
# 测试
weights = [2, 3, 4, 5]
values = [3, 4, 5, 6]
capacity = 8
result = knapsack(weights, values, capacity)
print("最大价值为:", result)
通过动态规划算法,我们可以高效地解决 0 - 1 背包问题。其核心在于合理定义状态和状态转移方程,将大问题分解为小问题,并通过保存子问题的解来避免重复计算。
要点 | 详情 |
---|---|
问题定义 | 给定物品重量、价值和背包容量,选择物品使总价值最大,每个物品只能选或不选 |
动态规划思想 | 分解大问题为小问题,保存子问题解避免重复计算 |
状态转移方程 | (dp[i][j]=\begin{cases}dp[i - 1][j],&j < w_i\\max(dp[i - 1][j], dp[i - 1][j - w_i]+v_i),&j\geq w_i\end{cases}) |
初始化 | (dp[0][j]=0),(dp[i][0]=0) |
时间复杂度 | (O(nC)) |
空间复杂度 | (O(nC)) |
0 - 1 背包问题不仅在算法竞赛中经常出现,在实际生活中也有广泛的应用,如资源分配、项目选择等。掌握了这个问题的解法,我们就能更好地应对各种类似的优化问题。