问题描述
有n件不相同的物品和一个背包,每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。
例:背包最大重量为4,物品为
重量 价值 物品0 1 1500 物品1 4 3000 物品2 3 2000 问背包能背的物品最大价值是多少?
算法思想
这道题目我是跟着卡尔哥的《代码随想录》学习的,但是对于小白可能不太友好,下面是《算法图解》的案例解析,这个案例与下面具体编码部分由部分出入:
假设你是个小偷,背着一个可装4磅东西的背包,可盗窃的商品有如下3件,为了让盗窃的商品价值最高,你该选择哪些商品?
动态规划思想:先解决小背包(子背包)问题,再逐步解决原来的问题,每个单元格计算的是从第一行到当前行(行对应商品)之间商品选择性放入后,当前容量背包(当前列对应容量)对应的最大价值。
开始填充背包,第一行是吉他行,意味着你将尝试将吉他装入背包。在每个单元格,都需要做一个简单的决定:偷不偷吉他?
因为当前只出现了吉他一个物品,而吉他是1磅,而4个背包的大小是1磅、2磅、3磅和4磅,足以放下这一个1磅的物品,所以(0,0),(0,1),(0,2),(0,3)均填入只放1个吉他的最大价值1500;
第二行是音响行,可偷的商品有吉他和音响(当前你还不能偷笔记本电脑,而只能偷音响和吉他),1磅的背包放不下4磅的音响,故(1,0)与(0,0)相同,最大价值还是1500;2磅的背包放不下4磅的音响,故(1,1)与(0,1)相同,最大价值还是1500;3磅的背包放不下4磅的音响,故(1,2)与(0,2)相同,最大价值还是1500;
注意,4磅的背包可以放下4磅的音响,且价值大于1500的音响,故放入音响,价值3000,背包容量剩下4-4=0磅,不再放入吉他,与(0,3)相比,3000>1500,最大价值为3000;
第三行是笔记本行,可偷的商品有吉他、音响和笔记本电脑,(2,0)、(2,1)两格由于不足以装下音响和笔记本,情况与(1,0)、(1,1)相同;背包容量为3磅时,可以放下3磅的笔记本,价值2000,背包容量剩下3-3=0磅,不再放入吉他,与(1,2)相比,3000>1500,最大价值为3000;
注意,4磅的背包可以像上面一格一样装入3000的音响,但我们先尝试装入笔记本,背包容量剩下4-3=1磅,此时1磅的容量可以看做一个子背包,笔记本已经放入背包了,这个子背包不受笔记本影响,而音响行正巧是笔记本还没有出现(即不考虑笔记本时已有物品的在1磅下最大价值,),(2,0)即我们想寻找的1磅背包产生的最大价值,所以总价值为2000+1500=3500;3500>3000,故填入3500
计算每个单元格的公式如下
动规五部曲
接下来按照动规五部曲详细分析这个题目:
dp数组含义
dp[i] [j]的含义,从物品0到物品i,任取物品放在容量为j的背包,其所产生的最大价值为dp[i] [g]。
dp数组存储的就是从第0行到当前行之间的所有物品,在各个子背包中,产生的最大价值是多少。
递推公式
不放物品i: dp[i-1] [j] 因为遍历到物品i对应的行时,物品i只有两个状态,取或者不取,当不取物品i时,dp[i] [j]值就等于上一行同样容量背包的最大价值。
放物品i: dp[i-1] [j-weight(i)]+value(i) 因为放入物品i时,dp[i] [j]的值就是当前商品的价value(i)加剩余空间( j-weight(i) )的价值,剩余空间的最大价值,就是商品0到商品i-1之间任取,既没放入商品i之前,剩余空间所对应子背包容量的最大价值。
当前容量背包只可能有两个结果,一个就是放物品i,另一个是不放物品i,所以就是在这两个公式之间取最大值,递推公式为:
dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1] [j - weight(i)] + value(i));
dp数组初始化
DP数组下标应从零开始,这样当背包容量为零时,可以在数组中找到相应的位置。
对DP数组进行初始化时,当背包容量为零时,所对应的最大价值自然为0;然后要初始化第一行物品,方便下面的递推,当背包容量大于等于一时,放入物品0的最大价值都是1500。
其余方格的值,初始化为任意值均可,因为其值都是由正上方的方格的值或者**左上方方格的值(根据递推公式可以看出)**所递推来的的。
遍历顺序
遍历该数组时,自然使用两层for循环,一层遍历背包容量,一层遍历物品号,其实哪一个先遍历都无所谓。因为每一个方格里的值都是由正上方的格和其左上方的格所递推来的,哪一种遍历顺序均不影响其递推。
具体代码
public class BagProblem {
public static void main(String[] args) {
int[] weight = {1,4,3};
int[] value = {1500,3000,2000};
int bagSize = 4;
testWeightBagProblem(weight,value,bagSize);
}
/**
* 动态规划获得结果
* @param weight 物品的重量
* @param value 物品的价值
* @param bagSize 背包的容量
*/
public static void testWeightBagProblem(int[] weight, int[] value, int bagSize){
// 创建dp数组
int goods = weight.length; // 获取物品的数量
int[][] dp = new int[goods][bagSize + 1];
// 初始化dp数组
// 创建数组后,其中默认的值就是0
for (int j = weight[0]; j <= bagSize; j++) {
dp[0][j] = value[0];
}
// 填充dp数组
for (int i = 1; i < weight.length; i++) {
for (int j = 1; j <= bagSize; j++) {
if (j < weight[i]) {
/**
* 当前背包的容量都没有当前物品i大的时候,是不放物品i的
* 那么前i-1个物品能放下的最大价值就是当前情况的最大价值
*/
dp[i][j] = dp[i-1][j];
} else {
/**
* 当前背包的容量可以放下物品i
* 那么此时分两种情况:
* 1、不放物品i
* 2、放物品i
* 比较这两种情况下,哪种背包中物品的最大价值最大
*/
dp[i][j] = Math.max(dp[i-1][j] , dp[i-1][j-weight[i]] + value[i]);
}
}
}
// 打印dp数组
for (int i = 0; i < goods; i++) {
for (int j = 0; j <= bagSize; j++) {
System.out.print(dp[i][j] + "\t");
}
System.out.println("\n");
}
}
}