动态规划学习---01背包问题
动态规划学习—01 背包问题
01 背包问题背景
- 拥有固定容量大小的背包
- 有 n 种物品, 每种物品只能取 1 次
- 需要返回背包能装下的最大价值
暴力解法
[dfs(深度优先搜索算法) / 回溯]
列出所有情况, 最后返回最大价值
动态规划解法
基础解法: 二维数组
动规五部曲:
- 确定
dp[i][j]
数组下标含义
- i 表示编号为**[0 - i]**的物品均判断过
- j 表示容量为 j 的背包
dp[i][j]
表示容量为 j 的背包, 在[0 - i]
物品中能装下的最大价值
- 确定
- 递推公式
根据
dp[i][j]
的定义可以知道:判断容量为 j 的背包最大值(即
dp[i][j]
)时只需要判断本次加入的物品**( i 物品)**是否需要放入背包
那么只有两种情况 :
- 放入 i 物品
由于需要放入 i 物品, 那么当前背包的价值, 应该是 :
判断完了
[0 到 i - 1]
物品价值的容量 j 的背包容量减去 i 物品的重量, 价值加上 i 物品的价值故背包价值为 :
dp[i - 1][j - weight[i]] + value[i]
不放入 i 物品
不放入 i 物品, 那么此时背包的价值容量应该都不变, 也就是和判断完了
[0 到 i - 1]
物品的背包最大价值相同
故背包价值为 : dp[i - 1] - 1[j]
要取得最大价值, 则此时背包的价值应该取两种情况的最大者
想到这里, 递推公式也就出来了 :
1 | /* |
- dp 数组的初始化
将容量为 0 的背包初始化为 0
第一种物品对应的容量满足的所有背包初始化成第一种物品的价值
其余部分的初始化无意义, 因为不会用到这些部分, 初始化任何数都可以
- 确定遍历顺序
由于使用的是二维数组, 定义了两个变量 i 和 j 背包和物品的依赖关系被解耦了,所以遍历顺序并不会影响结果的正确性
先遍历背包, 再遍历物品
_由数组下标要大于零可知, 当 j - weight[i]小于 0 时, 只能选择不放入物品 i , 因为背包的容量不够 _
先遍历物品, 再遍历背包
- 举例推导 dp 数组
- 假设背包容量为 3
所需容量 价值 物品 0 1 15 物品 1 2 20 物品 2 3 30 👇 为 i, 👉 为 j 0 1 2 3 0 0 15 15 15 1 0 15 20 35 2 0 15 20 35 斜体表示 : 初始化设置的值, 粗体表示 dp 数组遍历得到的值
进阶解法 : 一维数组
滚动数组理论基础
观察基础解法的递推公式, 发现新一层都需要使用上一层的值结果, 那么我们是否可以从后往前更新, 由于后面的数据需要用到上一层的结果, 此时, 上一层的结果还未更新, 保留在对应位置, 按照逆序更新, 就可以将二维数组压缩成一维数组
这种逆序更新的思想,就类似于一个滚筒(类似滚动转场)或者和翻页的感觉类似, 称为滚动数组的思想
dp 数组的含义
dp[j]
,将数组下标变量定义为 j, 目的是为了和一维数组保持一致便于理解- j 表示容量为 j 的背包
- dp[j] 表示容量为 j 的背包所能装载的最大价值
递推公式
同二维数组的思想相同, 根据滚动数组的定义则有 :
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]) //i 表示遍历物品时的编号
dp 数组初始化
和二维数组的想法类似, 应该将容量为 0 的和小于物品[0]重量的背包初始化为 0
(实际上应该初始化的值应该比最小的 value 还要小) : 防止 dp 数组的值被覆盖
遍历顺序
由于使用了滚动数组, 所以遍历顺序需要注意:
遍历顺序应该先遍历物品, 再遍历背包
递推的逻辑是,每次只判断当前该物品是否应该加入到 j 容量的背包
如果先遍历背包再遍历物品, 则需要则得到的正确数据会被重复刷新, 恢复成为遍历状态
(描述的不是很清楚, 但是建议通过对二维数组的模拟来体会)
遍历背包时, 应该采用倒序
因为滚动数组需要借用当前行当作上一行的数据, 所以需要从后往前更新, 上一行的数据才不会被提前覆盖
dp 数组的推导模拟
略