何为0-1背包问题?
拿或不拿,这是个问题。——————杰森.里昂(在下的英文名)
0-1背包听上去很高大上,实际就是日常的场景,你有个背包,一共就能装十斤的东西,现在你在一个藏宝洞,可以随便拿宝物,每个宝物价格不一样,重量也不一样,你现在想的是什么?搞钱呗!!!!怎么拿这一背包东西最值钱呗。
我咋个下手咧?
这时候你很头疼,我这数学也就是个小学水平,这怎么知道怎么拿能拿的最值钱?我连买菜都买不明白,没关系,好脑不如烂笔头,我们都记下来不就行了。
说干就干,先拿起来,宝物茫茫多,怎么个组合法呢?
想到了我们伟大的递推,1+1等于2对吧,1+2呢,就是1+1+1就是三!
你可以用这个神奇的递推,推出世界上所有的加法,这就是递推的神奇!
开干!
第一步:
我现在包装10斤我不会算,但是我包只能装一斤的话我会整,因为比一斤还轻的宝物很少,我一下就能算出来。我首先把所有宝物都排成一排(免得我记不住我算到哪了),然后我站在第一个宝物前面,如果它不满一斤,我就把它的价值算出来,记录。
第二步:
那包只能装两斤咋算?很容易,我继续往下一个宝物走去:
三种情况:
1.只能装下第二个宝物,第二个宝物价格比第一步算出来的高,扔掉头一个,装下第二个。
2.只能装下第二个宝物,第二个宝物价格比第一步算出来的低,不拿它,继续往前走。
2.能装的下这两个宝物,总价格就是第一个宝物价格和第二个宝物价格的和。
以此类推就可以推出十斤怎么装能装的价格最高了!
但是这个过程有个bug
你想,我现在背包只能装一斤,恰巧第一个得两斤重,但是最后一个宝物可以装进去的,所以我从前往后转一圈不够,我得每拿一个承重不同的袋子都得赚一圈,这样才能保证算出的是这个状态下能赚最多钱。
写成代码:
1.搞个二维数组 dp[i][j]
这个三维数组就是上文说到的“记录本”,用来记录前面算出来的最高价格。
其中i标识的是所选的宝物是前i个宝物。
j标识的是现在背包能够承受的重量。
dp[i][j]记录的就是在前i个宝物中挑选,背包承重量为j的情况下,最高的价值。
2.循环递推。
两次循环
第一层循环:比如前一个宝物的时候(即i=1),你要试试背包几斤的时候可以装进去(即遍历J从1到10)。
第二层循环:当背包只能装一斤时(即j=1),遍历所有宝物,看看能装哪些宝物。
附上代码:
package com.example;
/**
*
* @author jason 0-1背包问题
*/
public class ZeroAndOneBackcage {
//此为每个宝物的价值
static int[] v = { 0,1, 3, 4, 2, 1 };
// 此为每个宝物的重量
static int[] w = { 0,2, 3, 4, 3, 1 };
// 背包最大容量
static int maxv = 10;
// 宝物数量
static int maxn = 5;
public static int makeFortune(int[] v, int[] w) {
// 这里一共有6标识宝物的个数加一,为了避免讨论第一个元素。
int[][] totalVal = new int[maxn + 1][maxv];
for (int i = 1; i < maxn + 1; i++) {
for (int j = 0; j < maxv; j++) {
// 先将当前状态下背包的价值赋值为前i-1个宝物装入的最大值
totalVal[i][j] = totalVal[i - 1][j];
// 当第i个宝物的重量比j小时(也就是这个包是能放得下这个宝物的)
if (j >= w[i]) {
// 解释一下:现在能装的下第i个宝物了,那么我需要知道究竟是装了这个宝物我能拿的更多还是不装拿的更多,
// 不装就和前i-1个宝物最大价值一样,
// 装了呢就是这次装的宝物的价值(v[i])+剩下空间能装的最贵的价值(totalVal[i][j-w[i]])
totalVal[i][j] = Math.max(totalVal[i][j], totalVal[i][j - w[i]] + v[i]);
}
}
}
return totalVal[maxn][maxv-1];
}
public static void main(String[] args) {
System.out.println(makeFortune(v, w));
}
}