1.0-1背包算法
1.1.概述
0-1背包算法:有n个物品,每种物品只有一个,每个物品有自己的价值和重量,有一个容量为m的背包,求这个背包最多能装价值为多少的物品
Weight | Value | |
---|---|---|
物品0 | 1 | 15 |
物品1 | 3 | 20 |
物品2 | 4 | 30 |
背包的最大容量m = 4 |
1.2.暴力解法
每个物品只有两个状态:取与不取,可以进行暴力搜索,枚举所有的情况
- 物品0 + 物品1:m = 4,value = 35
- 物品0 + 物品2:m = 5,舍弃
- 物品1 + 物品2:m = 7,舍弃
暴力算法的结果为:物品0 + 物品1,最大价值为35;时间复杂度:O(n^2)
1.3.dp数组
1.3.1.含义
dp[i][j]
:表示[0,i]之间的物品任取放入容量为j的背包里
1.3.2.递推公式
在以下两种情况下有最大值:
- 不放物品i:
dp[i - 1][j]
,此时物品i的重量大于背包容量,物品i无法放入背包,所以最大价值和前一个相等 - 放入物品i:
dp[i - 1][j - Weight[i]] + Value[i]
,dp[i - 1][j - Weight[i]]就是放入物品i之前背包的最大价值,那加上物品i的价值Value[i]就等得到放入物品i的最大价值
递推公式:dp[i][j] = max(dp[i - 1][j],dp[i - 1][j - Weight[i]] + Value[i])
1.3.3.初始化
dp[i][j] | 背包容量:0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
物品0 | 0 | 15 | 15 | 15 | 15 |
物品1 | 0 | 15 | 15 | 20 | 35 |
物品2 | 0 | 15 | 15 | 20 | 35 |
-
首先初始化背包容量为0的一列:全部为0,
-
再初始化物品0的一行:容量大于等于物品0重量的字段都可以填入价值15
-
后续每个值根据左上方和正上方的值用递推公式可以得出:
- 当前背包容量小于新物品重量时直接使用上一个值
- 当前背包容量大于新物品重量时取旧值和新物品价值的最大值
1.3.4.完整代码
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][bagsize + 1];
//初始化:背包容量为0时,能获得的价值都为0
for (int i = 0; i < wlen; i++) {
dp[i][0] = value0;
}
//初始化只装物品0的情况
for (int i = 0; i < bagsize + 1; i++) {
if (i < weight[0]) {
dp[0][i] = 0;
} else {
dp[0][i] = value[0];
}
}
//遍历顺序:先遍历物品,再遍历背包容量
for (int i = 1; i < wlen; i++) {
for (int j = 1; j <= bagsize; 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 k = 0; k < wlen; k++) {
for (int l = 0; l <= bagsize; l++) {
System.out.print(dp[k][l] + " ");
}
System.out.print("\n");
}
System.out.println("\n");
}
}
}
1.4.一维dp数组
0-1背包问题还有一种更更加节省空间的解决方法,采用一维数组,就是在不加入新物品时就把dp[i - 1]那一层拷贝到dp[i]里,在加入新物品时就用新的价值,由此就可以只用dp[j]
- 一维dp数组dp[j]的定义:
容量为j的背包,所背物品价值的最大值为dp[j]
- 递推公式:
dp[j]可以由dp[j - Weight[i]]推导出来,dp[j]有两个选择,一是取自己dp[j](此时的自己就是代表上一轮赋值的最大价值,相当于dp[i - 1][j]),相当于不放新物品;另一个是取dp[j - Weight[i]] + Value[i],即放入新物品
所以递推公式:dp[j] = max(dp[j],dp[j - Weight[i]] + Value[i])
- 初始化:
根据dp[j]的定义知道dp[0]初始化为0,因为dp数组在推导的时候是取最大价值,所以其他下标初始化为0就可以了,让dp数组初始化时能取最大价值,不被初始值覆盖
- 遍历顺序:
for(int i = 0; i < weight.size(); i++) { // 遍历物品
for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
}
}
注意在遍历背包容量时是倒序遍历的,因为在求一个项的最大容量时,需要借助它前面的最大价值,如果不倒序遍历,前面的最大价值就先会被覆盖,无法求出下一项。
遍历流程:
- 初始化
dp[j]:{0,0,0,0,0}
-
遍历
- i = 0,Weight[i] = 1,Value[i] = 15
0 | 15 | 15 | 15 | 15 |
---|
- i = 1,Weight[i] = 3,Value[i] = 20
0 | 15 | 15 | 20 | 35 |
---|
- i = 2,Weight[i] = 4,Value[i] = 30
0 | 15 | 15 | 20 | 35 |
---|
- 完整代码
public static void main(String[] args) {
int[] weight = {1, 3, 4};
int[] value = {15, 20, 30};
int bagWight = 4;
testWeightBagProblem(weight, value, bagWight);
}
public static void testWeightBagProblem(int[] weight, int[] value, int bagWeight){
int wLen = weight.length;
//定义dp数组:dp[j]表示背包容量为j时,能获得的最大价值
int[] dp = new int[bagWeight + 1];
//遍历顺序:先遍历物品,再遍历背包容量
for (int i = 0; i < wLen; i++){
for (int j = bagWeight; j >= weight[i] ; j--){
dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
}
}
//打印dp数组
for (int j = 0; j <= bagWeight; j++){
System.out.print(dp[j] + " ");
}
}
2.完全背包
2.1.概述
在0-1背包的基础上,只要背包装得下,每件物品可以选择任意多件,从物品的角度来说,与之相关的策略不只有选或不选了,而是有取0件、取1件、取2件...直到取M/Weight件
2.2.dp数组
当背包当前容量M小于新物品的重量时:直接使用上一项的最大价值dp[i][j] = dp[i - 1][j]
若能放下新物品,判断添加多少个新物品可以达到最大价值:dp[i][j] = max(dp[i][j],dp[i - 1][j - k * Weight[i - 1]] + k * Value[i - 1])
2.3.完整代码
为了方便演示,将条件变为:
Weight | Value | |
---|---|---|
物品0 | 1 | 15 |
物品1 | 2 | 35 |
物品2 | 4 | 50 |
背包容量:4 |
class Knapsack {
private static int[] Value = {15, 35, 50};
private static int[] Weight = {1, 2, 4};
private static int T = 4;
private int[][] dp = new int[Value.length + 1][T + 1];
public void solve() {
for (int i = 1; i <= Value.length; i++) {
for (int j = 1; j <= T; j++) {
//在下面这个循环中,会在当前背包容量允许的条件下增加新物品的个数,最终该位置是最大价值
for (int k = 0; k * Weight[i - 1] <= j; k++) {
dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - k * Weight[i - 1]] + k * Value[i - 1]);
}
}
}
System.out.println("最大价值为:" + dp[Value.length][T]);
}
}
下面是每一次的添加逻辑: