01背包
标准的01背包问题:
有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。
暴力法:回溯搜索,O(2^n)
使用动态规划
1.二维dp数组
实现步骤:
1.确定dp数组及其下标含义
dp[i] [j] 代表 从0到i任选物品,放进容量为j的背包所得的最大价值
2.确定递推公式
对于当前点有两种情况
(1)放进背包
此时的价格变为dp[i] [j] = dp[i- 1] [j - weight[i]] + values[i]
(2)不放进背包
当前剩余容量不满足物品要求dp[i] [j] = dp[i - 1] [j]
最后要求价值最高。所以要在两者之间取最大值
递推数组:dp[i] [j] = max(dp[i-1] [j], dp[i-1] [j-weight[i]] + values[i] )
3.初始化dp数组
根据递推公式可知一个点是由该点左边和上边的点推导出来,先初始化dp[0] [j]一行和dp[i] [0]一列
dp[0] [j] 一行
满足第一个物品重量的初始化化为第一个物品的价值,不满足的初始化为0
dp[i] [0] 一列
没有重量,初始化为0即可
4.确定遍历顺序
因为当前点的状态是由上方和左上方的状态推导,所以一行一行遍历和一列一列遍历都可以
5.举例推导dp数组
可以举个例子或者根据测试用例和自己的递推公式进行推导,验证自己前面几步的正确性,或者在提交不过后打印日志,手动推导dp数组来debug
实现代码(以Java为例):
public class demo1 { //01背包理论实践 //二维dp数组 public static void main(String[] args) { int[] weight = {3, 4, 1}; int[] value = { 20, 30, 10}; int bagSize = 5; System.out.println(bag_01(weight, value, bagSize)); } /** * 有n件物品和一个最多能背重量为w 的背包。 * 第i件物品的重量是weight[i],得到的价值是value[i] * 。每件物品只能用一次,求解将哪些物品装入背包里物品 * 价值总和最大。 */ public static int bag_01(int[] weight, int[] value, int bagSize) { int n = weight.length; int m = bagSize; //行代表背包的容量,从0开始 //列表示物品,从0小标开始 int dp[][] = new int[n + 1][m + 1]; for(int i = 0; i <= n; i++) {//初始化列 dp[i][0] = 0; }//初始化列都为0可以省略,为了思路更有逻辑在这里列出来 for(int j = 1; j <= m && weight[0] <= j ; j++) { dp[0][j] = value[0]; } //遍历顺序按行按列两种都行 for(int i = 1; i < n; i++) { for(int j = 1; j <= m; j++) { if(j < weight[i]) dp[i][j] = dp[i - 1][j]; else { dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]); } } } //打印一下dp数组 for(int i = 0; i < n; i++) { for(int j = 0; j <= m; j++) { System.out.print(dp[i][j] + " "); } System.out.println(); } System.out.println("==============="); return dp[n - 1][m]; } }
2.一维dp数组(滚动数组)
因为之前是靠上一层推导出下一层,可以将二维矩阵压缩成一个一维dp数组,就是滚动数组,不断利用之前的值,即将上一次已经计算好的值拷贝到单层,变成一维数组
实现步骤:
1.确定dp数组及其下标含义
将前文所述的二维数组dp[i] [j]压缩为滚动数组dp[j],dp[j]代表了重量为j的背包所能容纳的最大价值
2.确定递推公式
(1)不放入背包,即背包容量小于物品
dp[j] = dp[j],因为此时的dp[j]就相当于二维的dp[i - 1] [j]
(2)放入背包
dp[j] = max(dp[j], dp[j - weight[i]] + value[i])
3.初始化dp数组
动态规划中初始化这步也很重要,需要想清楚
这题dp[j]代表重量为j的背包能容纳的最大价值,一开始都初始化为0就行,表示没有物品放入背包
4.确定遍历顺序
首先肯定要遍历每一个物品,物品在外层循环
内层循环遍历背包,注意遍历顺序,如果是从左到右遍历会出现一个物品重复使用的情况,因为此时外层循环是相同的物品,从左往右遍历使用了前面的状态相当于使用了相同的物品,所以要从右往左遍历。这里可以有一个优化的点,即内层循环不考虑容量小于当前物品重量的背包
5.举例推导dp数组
实现代码(以Java为例):
public class demo2 { public static void main(String[] args) { int[] weight = {1, 3, 4}; int[] value = {15, 20, 30}; int bagSize = 4; System.out.println(bog_02(weight, value, bagSize)); } //二维矩阵压缩为一维数组,滚动数组 public static int bog_02(int[] weight, int[] value, int bagSize) { int n = weight.length; int m = bagSize; //初始化,默认就是0 int[] dp = new int[m + 1]; for(int i = 0; i < n; i++) { for(int j = m; j >= weight[i]; j--) { dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]); } } //打印dp数组 for(int j = 0; j <= m; j++) { System.out.print(dp[j] + " "); } System.out.println(); System.out.println("=============="); return dp[m]; } }
理解01背包问题对于学习动态规划有重要的意义
以上内容是我对在代码随想录学习01背包问题知识点的总结以及收获,希望以上内容能帮助到大家