动态规划-01背包
01背包
可解决问题
- 数组的组合问题
给定一个数组nums,每个元素只能取一次,求从数组中取任意几个元素的和,等于target的所有组合数
背包大小为:target
递推方程:dp[j] += dp[j - nums[i]]
例题:目标和(难点在于转化为01背包问题)
问题描述
有n件物品和一个最多能容纳重量为bagSize物品的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。

例题解析
| 重量 | 价值 | |
| 物品0 | 1 | 15 |
| 物品1 | 3 | 20 |
| 物品2 | 4 | 30 |
背包容量为:4
二维dp数组
- 确定dp数组及其下表的含义
dp[i][j]: 表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少。
下标i:表示从[0-i]的物品里任意取物品(物品i可取可不取)
下标j:表示背包的容量大小
- 确定递推公式
对于物品i,有两种选择状态:取与不取
-
- 取物品i:
如果我们取了物品i,那么当前下标的价值(不是最大价值)由dp[i-1][j-weight[i]]决定。
(背包容量减去物品i的重量,表示取物品i)
dp[i-1][j-weight[i]]表示:放入物品i后,减去物品i的重量,在剩余容量中选取[0,i-1]物品的最大价值。
即:取物品i后的价值为 dp[i-1][j-weight[i]]+values[i] (不是最大价值)
-
- 不取物品i:
如果我们不取物品i,那么当前下标的价值(不是最大价值)由[0,i-1]任取物品的最大价值决定。
即:不取物品i的价值为 dp[i-1][j] (不是最大价值)
因此,在背包容量j中取[0,i]内任取物品的最大价值为:
dp[i][j]=max(dp[i-1][j-weight[i]]+values[i],dp[i-1][j])
- 初始化dp数据
由递推公式得知,dp[i][j]由dp[i-1][j]或dp[i-1][j-weight[i]]的到,因此,我们需要初始化当i=0是的最大价值.
初始化代码如下:
for(int j=weight[0];j<=bagSize;j++){
dp[0][j]=values[0];
}
- 确定遍历顺序
在本题中,先遍历背包,再遍历物品或者先遍历物品,在遍历背包其实都是可以的。
但为了便于理解,在题目没有特殊要求的情况下,选择先遍历物品再遍历背包。
遍历的代码如下:
//本题的weight[i]表示第i个物品的重量,相当于物品
for(int i=1;i<weight.length;i++){
for(int j=0;j<=bagSize;j++){
dp[i][j]=dp[i-1][j];//先将[0,i-1]最大价值复制过来,再讨论取不取物品i
if(j>=weight[i]){
dp[i][j]=Math.max(dp[i-1][j],dp[i-1][j-weight[i]]+values[i]);
}
}
}
完整测试代码:
完整java代码(点击展开)
public static void main(String[] args) {
int[] weight = {1, 3, 4};
int[] value = {15, 20, 30};
int bagsize = 4;
testweightbagproblem(weight, value, bagsize);
}
public static void testweightbagproblem(int[] weight, int[] value, int bagsize){
int wlen = weight.length, value0 = 0;
//定义dp数组:dp[i][j]表示背包容量为j时,前i个物品能获得的最大价值
int[][] dp = new int[wlen + 1][bagsize + 1];
//初始化:背包容量为0时,能获得的价值都为0
for (int i = 0; i <= wlen; i++){
dp[i][0] = value0;
}
//遍历顺序:先遍历物品,再遍历背包容量
for (int i = 1; i <= wlen; i++){
for (int j = 1; j <= bagsize; j++){
dp[i][j] = dp[i - 1][j];
if (j >= weight[i - 1]){
dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weight[i - 1]] + value[i - 1]);
}
}
}
//打印dp数组
for (int i = 0; i <= wlen; i++){
for (int j = 0; j <= bagsize; j++){
System.out.print(dp[i][j] + " ");
}
System.out.print("\n");
}
}
一维dp数组(先物品,后背包,不可颠倒)
在二维数组中,我们发现dp[i][j]=max(dp[i-1][j],dp[i-1][j-weight[i]]+weight[i])中,如果我们先把dp[i-1][j]的值赋值到dp[i][j]上,那么dp[i][j]=max(dp[i][j],dp[i][j-weight[i]]+weight[i]),我们发现,可以把二维dp[i][j]转为一维dp[j]。
- 确定dp数组及其下标的含义
dp[j]:表示容量为j的背包的最大价值
下标j:表示背包的容量
- 确定递推公式
对于物品i,有取与不取两种状态:
-
- 取物品i:
如果取物品i,则价值为dp[j-weight[i]]+values[i]
dp[j-weight[i]]:表示扣除物品i的重量后,剩余容量的最大价值.
-
- 不取物品i:
如果不选取物品i,则价值为 dp[j]
dp[j]:表示不容量j的价值,不选取i,不扣除i的总量
因此,对于容量为j的背包,最大价值为 dp[j]=max(dp[j],dp[j-weight[i]]+values[i])
- 初始化数据
根据递推公式,无论如何,dp[0]都会自己进行一次0和dp[0]+values[i]作比较,取最大值,则dp[j]数组默认初始化为零即可.
- 确定遍历顺序
一维dp数组的物品遍历顺序和二维相同,但是背包遍历应该由大到小遍历。
如果从小到大,表示物品可以无限选取属于完全背包。
01背包一维数组遍历代码如下:
for(int i=0;i<weight.length;i++){
for(int j=bigSize;j>=weight[i];j--){
dp[j]=Math.max(dp[j],dp[j-weight[i]]+values[i]);
}
}

浙公网安备 33010602011771号