详解0-1背包的状态转移表
初始理解0-1背包问题
首先,需要明确什么是0-1背包问题。0-1背包问题是一个经典的动态规划问题,其描述如下:
- 给定一组物品,每个物品有一个重量(weight)和一个价值(value)。
- 有一个容量有限的背包(capacity)。
- 目标是在不超过背包容量的前提下,选择一些物品装入背包,使得背包中物品的总价值最大。
- “0-1”意味着每个物品要么完整地选(1),要么不选(0),不能分割。
动态规划解决思路
动态规划通常用于解决具有重叠子问题和最优子结构性质的问题。对于0-1背包问题:
- 定义状态:通常使用一个二维数组
dp[i][w]
来表示考虑前i
个物品,在背包容量为w
时能够获得的最大价值。 - 状态转移方程:
- 对于第
i
个物品,有两种选择:- 不选:
dp[i][w] = dp[i-1][w]
(即最大价值与不考虑当前物品时相同)。 - 选(只有在当前物品的重量
weight[i]
≤w
时才可能):dp[i][w] = dp[i-1][w - weight[i]] + value[i]
。
- 不选:
- 因此,状态转移方程为:
dp[i][w] = max(dp[i-1][w], dp[i-1][w - weight[i]] + value[i]) if weight[i] <= welse dp[i-1][w]
- 对于第
- 初始化:
dp[0][w] = 0
对于所有w
,因为没有物品时最大价值为0。dp[i][0] = 0
对于所有i
,因为背包容量为0时不能装任何物品。
构建状态转移表
为了更好地理解,我决定通过一个具体的例子来构建状态转移表。假设有以下物品:
- 物品1:重量=2,价值=3
- 物品2:重量=3,价值=4
- 物品3:重量=4,价值=5
- 物品4:重量=5,价值=6
背包的总容量 W = 8
。
物品列表
物品编号 (i) | 重量 (weight[i]) | 价值 (value[i]) |
---|---|---|
1 | 2 | 3 |
2 | 3 | 4 |
3 | 4 | 5 |
4 | 5 | 6 |
状态转移表 dp[i][w]
表格的行表示物品编号 i
(从0到4,其中i=0表示没有物品),列表示背包容量 w
(从0到8)。
初始化:
dp[0][w] = 0
for allw
(没有物品时价值为0)。dp[i][0] = 0
for alli
(容量为0时不能装物品)。
现在逐步填充表格:
i=0 (没有物品):
w | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
i=1 (物品1: weight=2, value=3):
对于每个 w
,考虑是否选择物品1:
w < 2
: 不能选,dp[1][w] = dp[0][w] = 0
w >= 2
:- 不选:
dp[0][w] = 0
- 选:
dp[0][w-2] + 3 = 0 + 3 = 3
- 取 max:
max(0, 3) = 3
- 不选:
w | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|
1 | 0 | 0 | 3 | 3 | 3 | 3 | 3 | 3 | 3 |
i=2 (物品2: weight=3, value=4):
对于每个 w
:
w < 3
:dp[2][w] = dp[1][w]
w >= 3
:- 不选:
dp[1][w]
- 选:
dp[1][w-3] + 4
- 取 max
- 不选:
计算:
- w=0,1,2:
dp[2][w] = dp[1][w]
(0, 0, 3) - w=3:
- 不选:
dp[1][3] = 3
- 选:
dp[1][0] + 4 = 0 + 4 = 4
- max(3,4) = 4
- 不选:
- w=4:
- 不选:
dp[1][4] = 3
- 选:
dp[1][1] + 4 = 0 + 4 = 4
- max(3,4) = 4
- 不选:
- w=5:
- 不选:
dp[1][5] = 3
- 选:
dp[1][2] + 4 = 3 + 4 = 7
- max(3,7) = 7
- 不选:
- w=6:
- 不选:
dp[1][6] = 3
- 选:
dp[1][3] + 4 = 3 + 4 = 7
- max(3,7) = 7
- 不选:
- w=7:
- 不选:
dp[1][7] = 3
- 选:
dp[1][4] + 4 = 3 + 4 = 7
- max(3,7) = 7
- 不选:
- w=8:
- 不选:
dp[1][8] = 3
- 选:
dp[1][5] + 4 = 3 + 4 = 7
- max(3,7) = 7
- 不选:
w | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|
2 | 0 | 0 | 3 | 4 | 4 | 7 | 7 | 7 | 7 |
i=3 (物品3: weight=4, value=5):
对于每个 w
:
w < 4
:dp[3][w] = dp[2][w]
w >= 4
:- 不选:
dp[2][w]
- 选:
dp[2][w-4] + 5
- 取 max
- 不选:
计算:
- w=0,1,2,3:
dp[3][w] = dp[2][w]
(0, 0, 3, 4) - w=4:
- 不选:
dp[2][4] = 4
- 选:
dp[2][0] + 5 = 0 + 5 = 5
- max(4,5) = 5
- 不选:
- w=5:
- 不选:
dp[2][5] = 7
- 选:
dp[2][1] + 5 = 0 + 5 = 5
- max(7,5) = 7
- 不选:
- w=6:
- 不选:
dp[2][6] = 7
- 选:
dp[2][2] + 5 = 3 + 5 = 8
- max(7,8) = 8
- 不选:
- w=7:
- 不选:
dp[2][7] = 7
- 选:
dp[2][3] + 5 = 4 + 5 = 9
- max(7,9) = 9
- 不选:
- w=8:
- 不选:
dp[2][8] = 7
- 选:
dp[2][4] + 5 = 4 + 5 = 9
- max(7,9) = 9
- 不选:
w | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|
3 | 0 | 0 | 3 | 4 | 5 | 7 | 8 | 9 | 9 |
i=4 (物品4: weight=5, value=6):
对于每个 w
:
w < 5
:dp[4][w] = dp[3][w]
w >= 5
:- 不选:
dp[3][w]
- 选:
dp[3][w-5] + 6
- 取 max
- 不选:
计算:
- w=0,1,2,3,4:
dp[4][w] = dp[3][w]
(0, 0, 3, 4, 5) - w=5:
- 不选:
dp[3][5] = 7
- 选:
dp[3][0] + 6 = 0 + 6 = 6
- max(7,6) = 7
- 不选:
- w=6:
- 不选:
dp[3][6] = 8
- 选:
dp[3][1] + 6 = 0 + 6 = 6
- max(8,6) = 8
- 不选:
- w=7:
- 不选:
dp[3][7] = 9
- 选:
dp[3][2] + 6 = 3 + 6 = 9
- max(9,9) = 9
- 不选:
- w=8:
- 不选:
dp[3][8] = 9
- 选:
dp[3][3] + 6 = 4 + 6 = 10
- max(9,10) = 10
- 不选:
w | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|
4 | 0 | 0 | 3 | 4 | 5 | 7 | 8 | 9 | 10 |
完整的状态转移表
将上述所有行的 dp[i][w]
整合起来:
i \ w | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 3 | 3 | 3 | 3 | 3 | 3 | 3 |
2 | 0 | 0 | 3 | 4 | 4 | 7 | 7 | 7 | 7 |
3 | 0 | 0 | 3 | 4 | 5 | 7 | 8 | 9 | 9 |
4 | 0 | 0 | 3 | 4 | 5 | 7 | 8 | 9 | 10 |
解读表格
- 表格的最后一个单元格
dp[4][8] = 10
表示在考虑前4个物品,背包容量为8时的最大价值为10。 - 要找出具体选择的物品,可以回溯:
dp[4][8] = 10
来自dp[3][3] + 6
,所以选择了物品4(重量5,价值6)。- 然后看
dp[3][3] = 4
,来自dp[2][0] + 4
,所以选择了物品2(重量3,价值4)。 dp[2][0] = 0
,没有更多物品。- 因此,选择的物品是物品2和物品4,总重量=3+5=8,总价值=4+6=10。
验证
选择的物品:
- 物品2:重量3,价值4
- 物品4:重量5,价值6
总重量:3 + 5 = 8 ≤ 8(满足容量)
总价值:4 + 6 = 10
检查其他组合:
- 物品1 + 物品3:重量2 + 4 = 6,价值3 + 5 = 8 < 10
- 物品1 + 物品2 + 物品3:重量2 + 3 + 4 = 9 > 8(超重)
- 物品3 + 物品4:重量4 + 5 = 9 > 8(超重)
确实,选择物品2和物品4能得到最大价值10。
状态转移表的可视化
为了更清晰地展示状态转移的过程,以下是 dp[i][w]
的填充过程:
-
初始化:
- 第一行(i=0)全部为0。
- 第一列(w=0)全部为0。
-
i=1 (物品1: weight=2, value=3):
- w=2: max(dp[0][2], dp[0][0]+3) = max(0, 3) = 3
- w=3: max(dp[0][3], dp[0][1]+3) = max(0, 3) = 3
- … 直到w=8都是3
-
i=2 (物品2: weight=3, value=4):
- w=3: max(dp[1][3], dp[1][0]+4) = max(3, 4) = 4
- w=4: max(dp[1][4], dp[1][1]+4) = max(3, 4) = 4
- w=5: max(dp[1][5], dp[1][2]+4) = max(3, 7) = 7
- … w=6,7,8: 类似
-
i=3 (物品3: weight=4, value=5):
- w=4: max(dp[2][4], dp[2][0]+5) = max(4, 5) = 5
- w=5: max(dp[2][5], dp[2][1]+5) = max(7, 5) = 7
- w=6: max(dp[2][6], dp[2][2]+5) = max(7, 8) = 8
- w=7: max(dp[2][7], dp[2][3]+5) = max(7, 9) = 9
- w=8: max(dp[2][8], dp[2][4]+5) = max(7, 9) = 9
-
i=4 (物品4: weight=5, value=6):
- w=5: max(dp[3][5], dp[3][0]+6) = max(7, 6) = 7
- w=6: max(dp[3][6], dp[3][1]+6) = max(8, 6) = 8
- w=7: max(dp[3][7], dp[3][2]+6) = max(9, 9) = 9
- w=8: max(dp[3][8], dp[3][3]+6) = max(9, 10) = 10
可能的疑问与验证
在构建表格的过程中,可能会有以下疑问:
-
为什么在i=2, w=5时选择物品2?
- 不选:
dp[1][5] = 3
- 选:
dp[1][2] + 4 = 3 + 4 = 7
- 选择物品2后,剩余容量为5-3=2,
dp[1][2]
表示在容量为2时前1个物品的最大价值是3(物品1)。 - 因此总价值为物品2的价值4 + 物品1的价值3 = 7。
- 不选:
-
为什么在i=4, w=8时选择物品4?
- 不选:
dp[3][8] = 9
- 选:
dp[3][3] + 6 = 4 + 6 = 10
dp[3][3]
表示在容量为3时前3个物品的最大价值是4(选择物品2)。- 因此总价值为物品4的价值6 + 物品2的价值4 = 10。
- 不选:
总结
通过构建这个状态转移表,我们可以清晰地看到每一步是如何基于之前的结果做出决策的。动态规划的核心在于利用子问题的解来构建更大问题的解,避免重复计算。在0-1背包问题中,状态转移表直观地展示了在不同物品选择和不同背包容量下的最优解。
最终的状态转移表
以下是完整的 dp[i][w]
表格:
i \ w | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 3 | 3 | 3 | 3 | 3 | 3 | 3 |
2 | 0 | 0 | 3 | 4 | 4 | 7 | 7 | 7 | 7 |
3 | 0 | 0 | 3 | 4 | 5 | 7 | 8 | 9 | 9 |
4 | 0 | 0 | 3 | 4 | 5 | 7 | 8 | 9 | 10 |
最优解
从表格中可以看出,dp[4][8] = 10
是最大价值。通过回溯,可以确定选择的物品是物品2和物品4。
补充说明
在实际编程实现时,可以优化空间复杂度,使用一维数组来替代二维数组,因为 dp[i][w]
只依赖于 dp[i-1][...]
。但为了理解和教学目的,二维的状态转移表更为直观。
其他例子验证
为了确保理解正确,我再举一个简单的例子:
例子:
- 物品1:重量=1,价值=1
- 物品2:重量=2,价值=2
- 物品3:重量=3,价值=3
- 背包容量 W=4
状态转移表:
i \ w | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 1 | 1 | 1 | 1 |
2 | 0 | 1 | 2 | 3 | 3 |
3 | 0 | 1 | 2 | 3 | 4 |
最优解:dp[3][4] = 4
,选择物品1和物品3(重量1+3=4,价值1+3=4)。
结论
通过构建和填充状态转移表,可以系统地解决0-1背包问题。关键在于:
- 正确定义状态
dp[i][w]
。 - 正确写出状态转移方程。
- 初始化边界条件。
- 按顺序填充表格,确保在计算
dp[i][w]
时所需的子问题已经解决。 - 通过回溯找出具体选择的物品。
这种方法不仅适用于0-1背包问题,也是理解和应用动态规划的通用框架。