一、01背包
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
第 i件物品的体积是 vi,价值是 wi。求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。输出最大价值。(下图是例子,一下分析均由此图得来)
1、分析:
(1) 确定dp数组以及下标的含义
对于背包问题,有⼀种写法, 是使⽤⼆维数组,即dp[i][j] 表⽰从下标为[0-i]的物品⾥任意
取,放进体积为j的背包(即总体积不超过j),价值总和最⼤是多少。
dp[i][j]:表示所有选法集合中,只从前i个物品中选,并且总体积≤j的选法的集合,它的值是这个集合中每一个选法的最大值.
(2)划分集合、确定递推公式
划分集合:选不选物品i
不选i:由dp[i - 1][j]推出,即背包容量为j,⾥⾯不放物品i的最⼤价值,此时dp[i][j]就是
dp[i - 1][j]
选i:由dp[i - 1][j - w[i]]推出,dp[i - 1][j - w[i]] 为背包容量为j - w[i]的时
候不放物品i的最⼤价值,那么dp[i - 1][j - w[i]] + value[i] (物品i的价值),
就是背包放物品i得到的最⼤价值
所以递归公式: dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
(3)dp数组的初始化
关于初始化,⼀定要和dp数组的定义吻合,否则到递推公式的时候就会越来越乱。
⾸先从dp[i][j]的定义出发,如果背包容量j为0的话,即dp[i][0],⽆论是选取哪些物品,背包
价值总和⼀定为0。如图:
在看其他情况。
状态转移⽅程 dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]); 可以看出i 是由 i-1推导出来,那么i为0的时候就⼀定要初始化。
dp[0][j],即:i为0,存放编号0的物品的时候,各个容量的背包所能存放的最⼤价值。
那么很明显当 j < weight[0]的时候,dp[0][j] 应该是 0,因为背包容量⽐编号0的物品重量还
⼩。
当j >= weight[0]是,dp[0][j] 应该是value[0],因为背包容量放⾜够放编号0物品。
代码初始化如下:
// 当然这⼀步,如果把dp数组预先初始化为0了,这⼀步就可以省略,但很多同学应该没有想清楚这⼀点。
for (int j = 0 ; j < weight[0]; j++) dp[0][j] = 0;
// 正序遍历
for (int j = weight[0]; j <= bagWeight; j++) dp[0][j] = value[0];
此时dp数组初始化情况如图所⽰:
其实从递归公式: dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]); 可以看出dp[i][j] 是由左上⽅数值推导出来的,那么 其他下标初始为什么数值都可以,因为都会被覆盖。但只不过⼀开始就统⼀把dp数组统⼀初始为0,更⽅便⼀些。
(4)确定遍历顺序
在如下图中,可以看出,有两个遍历的维度:物品与背包重量
那么问题来了,先遍历物品还是先遍历背包重量呢?
其实都可以!! 但是先遍历物品更好理解。
先遍历物品,然后遍历背包重量的代码:
// weight数组的⼤⼩ 就是物品个数
// 遍历物品
for(int i = 1; i < weight.size(); i++)
{ // 遍历背包容量
for(int j = 0; j <= bagWeight; j++)
{
if (j < weight[i]) dp[i][j] = dp[i - 1][j]; // 这个是为了展现dp数组⾥元素的变化
else dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
}
}
先遍历背包,再遍历物品,也是可以的!(注意我这⾥使⽤的⼆维dp数组)
2、代码: