小型企业网站设计教程app软件开发技术pdf百度云
好的,没问题。我们从零开始,忘记所有术语,只看这个问题的本质。
0. 问题的本质是什么?
想象你有一个背包,它能装的东西有限(容量为5)。
你面前有一堆宝贝,每个宝贝都有自己的重量和价值。
- 宝贝A:重1,值6
- 宝贝B:重2,值10
- 宝贝C:重3,值12
你面临一个选择:要拿哪个宝贝,才能让背包里的总价值最高?
每个宝贝你只能拿一次,这就是“0-1背包”的“0-1”——要么拿(1),要么不拿(0)。
1. 为什么不能“贪心”?
“贪心”就是你觉得哪个最划算就先拿哪个。比如你可能会想:
- 宝贝A:价值/重量 = 6/1 = 6
- 宝贝B:价值/重量 = 10/2 = 5
- 宝贝C:价值/重量 = 12/3 = 4
你可能会先拿宝贝A(最划算),然后背包容量还剩4,再拿宝贝B(次划算),背包容量还剩2,装不进宝贝C了。总价值是 6 + 10 = 16。
但更好的方案是:拿宝贝B和宝贝C,总重量是2+3=5,正好装满,总价值是10+12=22。
所以,简单的贪心算法是错的。我们需要一个更强大的方法,能考虑所有可能性,但又不会太慢。这个方法就是动态规划。
2. 动态规划的定义和逻辑
动态规划就像一个“填表格”的游戏。我们不是直接寻找最终答案,而是一步一步地,有条理地解决一个个小问题,最后从小问题的答案里推导出最终的答案。
我们来定义这个表格(二维数组 dp):
dp[i][j] 的意思是:“只考虑前 i 个宝贝,在背包容量为 j 的情况下,能得到的最大价值。”
我们的最终目标,就是要填完整个表格,找到 dp[3][5] 的值(因为有3个宝贝,背包容量是5),这个值就是最终答案。
3. 如何一步步填表格?
我们用你提供的测试数据来填这个 dp 表格。
- 宝贝:A(1,6), B(2,10), C(3,12)
- 背包容量:W=5
表格结构:
i (物品数) \ j (容量) | 0 | 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|---|---|
| 0 (无物品) | 0 | 0 | 0 | 0 | 0 | 0 |
| 1 (物品A) | ? | ? | ? | ? | ? | ? |
| 2 (物品A,B) | ? | ? | ? | ? | ? | ? |
| 3 (物品A,B,C) | ? | ? | ? | ? | ? | ? |
填表逻辑(核心):
我们每次填一个格子 dp[i][j],都要做个决定:拿不拿第 i 个宝贝?
-
如果你不拿第
i个宝贝:
那你的最大价值,就和只考虑前i-1个宝贝时,容量为j的最大价值一样。
也就是dp[i-1][j]。 -
如果你拿第
i个宝贝:
前提是你的背包容量j足够大(j >= 第 i 个宝贝的重量)。
如果你拿了,你的背包还剩下j - 第 i 个宝贝的重量的容量。
剩下的容量能装多少价值?答案在表格的上一行,也就是dp[i-1][j - 第 i 个宝贝的重量]。
所以,总价值是dp[i-1][j - 第 i 个宝贝的重量]+第 i 个宝贝的价值。
我们取这两种情况中价值最大的一个,作为 dp[i][j] 的值。
4. 实际推演
宝贝A (重1, 值6)
- 填第1行:
dp[1][0]:容量0,装不下A,价值为0。dp[1][1]:容量1,可以装下A。拿了A,价值是6。dp[1][2]:容量2,可以装下A,价值是6。- …
- **总结:**只要容量大于等于1,就能装下宝贝A,价值都是6。
dp第1行:[0, 6, 6, 6, 6, 6]
宝贝B (重2, 值10)
- 填第2行:
dp[2][0]:容量0,装不下B,沿用上一行(不拿B)的值,dp[1][0]=0。dp[2][1]:容量1,装不下B,沿用上一行,dp[1][1]=6。dp[2][2]:容量2,可以装下B。- 不拿B:价值是
dp[1][2]=6 - 拿B:价值是
dp[1][2-2]+10 =dp[1][0]+10 = 0+10 = 10 max(6, 10)= 10
- 不拿B:价值是
dp[2][3]:容量3,可以装下B。- 不拿B:价值是
dp[1][3]=6 - 拿B:价值是
dp[1][3-2]+10 =dp[1][1]+10 = 6+10 = 16 max(6, 16)= 16
- 不拿B:价值是
dp[2][4]:容量4,可以装下B。- 不拿B:价值是
dp[1][4]=6 - 拿B:价值是
dp[1][4-2]+10 =dp[1][2]+10 = 6+10 = 16 max(6, 16)= 16
- 不拿B:价值是
dp[2][5]:容量5,可以装下B。- 不拿B:价值是
dp[1][5]=6 - 拿B:价值是
dp[1][5-2]+10 =dp[1][3]+10 = 6+10 = 16 max(6, 16)= 16
- 不拿B:价值是
dp第2行:[0, 6, 10, 16, 16, 16]
宝贝C (重3, 值12)
- 填第3行:
dp[3][0]:容量0,装不下C,沿用dp[2][0]=0。dp[3][1]:容量1,装不下C,沿用dp[2][1]=6。dp[3][2]:容量2,装不下C,沿用dp[2][2]=10。dp[3][3]:容量3,可以装下C。- 不拿C:价值是
dp[2][3]=16 - 拿C:价值是
dp[2][3-3]+12 =dp[2][0]+12 = 0+12 = 12 max(16, 12)= 16
- 不拿C:价值是
dp[3][4]:容量4,可以装下C。- 不拿C:价值是
dp[2][4]=16 - 拿C:价值是
dp[2][4-3]+12 =dp[2][1]+12 = 6+12 = 18 max(16, 18)= 18
- 不拿C:价值是
dp[3][5]:容量5,可以装下C。- 不拿C:价值是
dp[2][5]=16 - 拿C:价值是
dp[2][5-3]+12 =dp[2][2]+12 = 10+12 = 22 max(16, 22)= 22
- 不拿C:价值是
最终,我们填完了所有格子。表格的右下角 dp[3][5] 的值是 22,这就是最终的答案。
总结
- 定义:动态规划是把一个大问题拆解成互相联系的小问题,然后通过解决这些小问题,来逐步得到大问题的答案。
- 逻辑:我们用
dp[i][j]这样的表格来存储每个小问题的答案。 - 转移方程:我们用
dp[i-1][...]的答案,来计算dp[i][...]的答案。这就是“状态转移方程”,它连接了不同的“小问题”。
现在你觉得理解起来有没有更容易一些?
