微信登录

动态规划算法 - 背包问题 - 0-1 背包问题的求解

动态规划算法 - 背包问题 - 0 - 1 背包问题的求解

引言

在算法的世界里,背包问题是一个经典且极具实用性的问题,而 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) 时,我们有两种选择:

  • 不选择第 (i) 个物品,此时 (dp[i][j]=dp[i - 1][j])。
  • 选择第 (i) 个物品,此时 (dp[i][j]=dp[i - 1][j - w_i]+v_i),即先将背包容量减去第 (i) 个物品的重量,然后考虑前 (i - 1) 个物品的最大价值,再加上第 (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。

代码实现(Python)

  1. def knapsack(weights, values, capacity):
  2. n = len(weights)
  3. # 创建二维数组 dp
  4. dp = [[0 for _ in range(capacity + 1)] for _ in range(n + 1)]
  5. # 填充 dp 数组
  6. for i in range(1, n + 1):
  7. for j in range(1, capacity + 1):
  8. if j < weights[i - 1]:
  9. dp[i][j] = dp[i - 1][j]
  10. else:
  11. dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weights[i - 1]] + values[i - 1])
  12. return dp[n][capacity]
  13. # 测试
  14. weights = [2, 3, 4, 5]
  15. values = [3, 4, 5, 6]
  16. capacity = 8
  17. result = knapsack(weights, values, capacity)
  18. print("最大价值为:", result)

复杂度分析

  • 时间复杂度:代码中有两层嵌套循环,外层循环遍历 (n) 个物品,内层循环遍历背包容量 (C),因此时间复杂度为 (O(nC))。
  • 空间复杂度:使用了一个二维数组 (dp) 来保存中间结果,数组的大小为 ((n + 1)\times(C + 1)),因此空间复杂度为 (O(nC))。

总结

通过动态规划算法,我们可以高效地解决 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 背包问题不仅在算法竞赛中经常出现,在实际生活中也有广泛的应用,如资源分配、项目选择等。掌握了这个问题的解法,我们就能更好地应对各种类似的优化问题。