有N件物品和⼀个最多能被重量为W的背包。第i件物品的重量是weight[i],得到的价值是value[i]。每件物品只能⽤⼀次,求解将哪些物品装入背包⾥物品价值总和最⼤。
在题目中我们假设有三件物品
重量 价值
物品1 1 15
物品2 3 20
物品3 4 30
那么假设背包的最大承重量给定为最重物品的重量,求解可以在不超重情况下背包存放的最大价值
01背包问题是动态规划算法中最基础的也是最为重要的一类问题,其解题过程比较抽象,数组的定义与爬楼梯等问题相比,还是难以理解。下面我们就来探索一下这个问题的解法。
首先题目的例子不难,我们来猜测一下,重量为4的背包放以上三件物品,那么有多种放法,1、2、3、1和2四种放法,其中1和2最贵,那么本题的答案就是35。下面我们来理一理思路。
为什么我们舍弃了单放一个3而选择了1和2?因为1和2比3贵嘛!好吧,这里是正常人的思维,下面我们来打开思维。假设背包最大承重为3,我们有两种放法,一个是1,一个是2,为什么我们选择2?因为我们首先选择了1,当选2的时候发现背包容量不够,所以我们就有了取出物品1的想法。当我们取出物品1,放入物品2之后我们就认定了最贵就是单放物品2了吗?显然不是,我们把取出物品1后放入物品2的总价值与没取出物品1的背包物品总价值进行了对比,发现原来把1拿出来后放2,比不放2价值更大,所以选择了后者。
现在回过来看,为什么我们选择物品1和物品2而不是单单一个物品3呢?因为我们取出物品1和2后放入物品3的总价值低于不放物品3的总价值所以我们没有取出物品。这就是核心的递推公式呀!!!
递推公式
当前承重量的背包最大容纳价值 = max(腾出新物品空位后的背包承重量最大价值 + 新物品的价值,不加入新物品的最大价值);
定义一个二维数组,每一列为从0-max解放的背包重量,每一行表示新解放的物品
当只能放物品1时,背包容量从0-max,每个容量对应的最大价值就为
0 1 2 3 4
15 0 15 15 15 15
20
30
当解放物品2,那么背包容量从0-max,每个容量对应的最大价值就为
0 1 2 3 4
15 0 15 15 15 15
20 0 15 15 20 35
30
当解放物品2,那么背包容量从0-max,每个容量对应的最大价值就为
0 1 2 3 4
15 0 15 15 15 15
20 0 15 15 20 35
30 0 15 15 20 35
例1:以第三行第四个的价值35为例
35 = max(不加入第三个物品的最大价值(第二行第四列),去除加入物品重量后的最大价值(第二行第一列) + 新物品价值)
public class Bag01 {
public static void main(String[] args) {
int[] weight = new int[]{1, 3, 4};
int[] value = new int[]{15, 20, 30};
int i = maxValue(weight, value);
System.out.println(i);
}
public static int maxValue(int[] weight, int[] value) {
//根据weight确定二维数组的行宽,数组的最后一个元素索引为最大重量
//根据背包中物品的种类确定二维数组的列宽
int[][] realValue = new int[weight.length][weight[weight.length - 1] + 1];
// 0 1 2 3 4
// 15 0 15 15 15 15
// 20
// 30
//当重量为0,1,2,3,4时,背包只能存放物品1的情况下,最大价值为
for (int i = weight[weight.length - 1]; i >= 0; i--) {
//如果该背包容量能存放下的话,那么就存入第一个物品
if (i >= weight[0]) {
realValue[0][i] = value[0];
}
}
//当重量为0时,背包无法存放物品
for (int i = 0; i < realValue.length; i++) {
realValue[i][0] = 0;
}
//初始化内容
//数组的列表示物品的种类,数组的的横向表示背包的承重量
//数组的第一层就表示 达到上限前 背包的最大沉重量 依次可以存放的 最贵物品的 总价值
//数组的第一列就表示 当重量为0时,背包无法存放物品,最大价值为0
//观察第二层数据
//当解放第二个物品时
// 0 1 2 3 4
// 15 0 15 15 15 15
// 20 0 15 15 20 35
// 30 0
//重量为0,1,2时,背包都放不下第二个物品,所以最大价值继承没解放第二个物品时的价值
//重量为3时,背包的可以容纳进第二个物品,那么比较:
// 如果不加入第二个物品,那么最后的价格 就保留未解放第二个物品时 该承重的最大价格
// 如果加入第二个物品,那么最后的价格 就是除去该物品重量后背包承重的最大价格 + 第二个物品的价格
// 很显然,20 + 0 > 15 所以最大价格为20
//重量为4时,重复重量为3的操作 20 + 15 > 30,所以写入35
//从第二行开始
for (int i = 1; i < realValue.length; i++) {
//从第二列开始
for (int j = 1; j < realValue[0].length; j++) {
//如果新加入物品的重量小于最大承重
if (weight[i] <= j) {
//比较
realValue[i][j] = Math.max(realValue[i - 1][j], realValue[i - 1][j - weight[i]] + value[i]);
}
//如果新加入物品的重量大于最大承重
else {
realValue[i][j] = realValue[i - 1][j];
}
}
}
//遍历一下数组看看长啥样
for (int[] ints : realValue) {
for (int i = 0; i < ints.length; i++) {
System.out.print(ints[i] + " ");
}
System.out.println();
}
//返回数组右下角的最终值,即解放所有物品所有重量后的值
return realValue[realValue.length - 1][realValue[0].length - 1];
}
}