从小偷入室行窃谈起:
话说一小偷深更半夜去偷东西,带了一个背包,但是这个背包只能装下10kg的物品(这个小偷也是够笨的不整个大点的包),推开了房门,看到了什么?(这不是废话嘛,当然看到的全部都是贵重物品啊)。小偷发现房间没人,小偷暗喜这就好办了,接下来就是到处搜寻贵重物品,功夫不负有心人(貌似这句话放在这里不恰当啊),一共找到了5件贵重物品(这户人间真是穷啊),暂且叫做a、b、c、d、e吧,这五件物品有重量也有价值,分别如下:
a | b | c | d | e | |
重量 | 4kg | 5kg | 6kg | 2kg | 2kg |
价值 | 6 | 4 | 5 | 3 | 6 |
小偷看着这五件贵重物品发愣了,自己背包只能装10kg,要怎么装才能让带走东西的价值最多呢?(不要想着用口袋再装一点,就是那么多限制)
画个表格计算一下:
话说这年头日子真难混啊,就连做个小偷也不容易啊(更何况学计算机的呢,不说了,说多了都是泪啊)。小偷拿出纸和笔开始计算了起来(可见这小偷是有备而来啊,这心机......)。这个小偷画了这样一个二维的表格:
0kg | 1kg | 2kg | 3kg | 4kg | 5kg | 6kg | 7kg | 8kg | 9kg | 10kg | |
不放物品 | |||||||||||
放第一个物品a(4kg,6) | |||||||||||
放第二个物品b(5kg,4) | |||||||||||
放第三个物品c(6kg,5) | |||||||||||
放第四个物品d(2kg,3) | |||||||||||
放第五个物品e(2kg,6) |
题外话:给大家解释一下这个表格的含义:
行(下面用i表示坐标):一共有6种选择(5件物品怎么多了一个选择?选择什么都不装嘛!),此件物品放不放进自己的背包取决于上个物品的选择(这个理解起来有难度,下面还会进行解读的,读完整篇文章你就会懂了。)
列(下面用k表示坐标):0-10表示背包的能容纳的重量,小偷可以选择什么都不装(白偷一次,空手回去),但是最多也只能装10kg。
行和列的交叉:处就是装在背包里面物品的价值,用f[i,k]表示背包装有物品的总价值,i表示哪一行,k表示背包能容纳的重量,记住这两个的含义下文还会用到,举例f[2,8]表示第2行(放第二个物品b)背包容量只有第8列(8kg)时背包物品价值最大价值(当然坐标从0开始算起)
先初始一下数组:
这个步骤很简单,第0行(不放物品时,不管你的背包能装多少东西价值都为0)、第0列(背包能容纳的的总量为0,什么都装不了,背包的价值还是为0)全部都是0。下面是初始化后的状态:
0kg | 1kg | 2kg | 3kg | 4kg | 5kg | 6kg | 7kg | 8kg | 9kg | 10kg | |
不放物品 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
放第一个物品a(4kg,6) | 0 | ||||||||||
放第二个物品b(5kg,4) | 0 | ||||||||||
放第三个物品c(6kg,5) | 0 | ||||||||||
放第四个物品d(2kg,3) | 0 | ||||||||||
放第五个物品e(2kg,6) | 0 |
开始放入a物品了哟:
旁白:表格每行的填入是从左至右(也就是随着背包容量的增大来装东西)
忙活了半天终于可以开工了,a物品到底要不要放进去取决于两个因素,第一是a有4kg重,只有背包大于等于4kg的时候才能装进去(也就是说当i=1,k<4时f[i,k]=0);第二是当背包的重量大于等于4kg时要不要把a放进去呢(全篇的重点就在这里);Put or not put,that's a question。读者此时应该会有疑惑,肯定是把a放进去啊,反正在放a之前什么都没有,当然这样考虑我也同意,只是我要说的是,这是一个算法问题,必须找寻到规律所在,一环扣一环。现在我们按照刚才说的两个因数填一下第1行(时刻记得坐标是从0开始的),背包小于4kg时的值与上一行相同,f(1,1)=f(0,1)=0,f(1,2)=f(0,2)=0,f(1,3)=f(0,3)=0。随着背包能容纳的重量增大,当k=4时(背包能容纳4kg),现在到了选择的时刻了,将a放进去跟不放相比较,哪个价值要大一些,哪个价值大就选择哪个。将a放进去后背包的价值为6,a不放进去背包的价值为0,这里就要思考这个0对应的是表格里面的那个0了,不放a就回到了上一行的状态(第0行,i=0),a的重量是4kg,此时背包的的容量是4kg,不放a要用背包现在的容量4kg减去a的重量4kg,得到的值为0,即k=0,回到了f[0,0]的状态。找放入该物品之前的状态是一个非常关键的问题。那就再试着找一个状态吧,当背包为能容纳10kg时,a放进去背包总价值是6,不放进对应的状态对应f[0,6]价值为0,所以选总价值大的那个,即放进去。
0kg | 1kg | 2kg | 3kg | 4kg | 5kg | 6kg | 7kg | 8kg | 9kg | 10kg | |
不放物品 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
放第一个物品a(4kg,6) | 0 | 0 | 0 | 0 | 6 | 6 | 6 | 6 | 6 | 6 | 6 |
放第二个物品b(5kg,4) | 0 | ||||||||||
放第三个物品c(6kg,5) | 0 | ||||||||||
放第四个物品d(2kg,3) | 0 | ||||||||||
放第五个物品入e(2kg,6) | 0 |
开始放入b物品了哟:
放b物品与放a物品一样了(可不要因为一样就随便看,这里才是重头戏),b物品放不放进背包取决于两个因素,第一b有5kg的总量,背包容量有没有达到5kg,第二背包能容纳5kg,b放不放进去。当0<k<5时,背包价值与上一行相同,k=5时,不放b背包的价值与同列的上一行相同,价值为6,如果要把b放进去,必须回到放b之前的状态,按照之前的方法找到放b之前的状态f[1,0],f[1,0]的值(即背包的价值)是0,放入b后价值是0+4(注意0),比不放b的价值6要小,所以选择不把b放进背包,同理,k=6,7,8时一样。当k=9的时候,这时需要注意啦:不放b的价值是6,放b进背包则首先回到放b之前的状态,即f[1,4]的价值,发f[1,4]的价值是6,将b放进去的价值是6+4=10,大于不放b的价值6,所以此时将b放进去,即f[2,9]=10
0kg | 1kg | 2kg | 3kg | 4kg | 5kg | 6kg | 7kg | 8kg | 9kg | 10kg | |
不放物品 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
放第一个物品a(4kg,6) | 0 | 0 | 0 | 0 | 6 | 6 | 6 | 6 | 6 | 6 | 6 |
放第二个物品b(5kg,4) | 0 | 0 | 0 | 0 | 6 | 6 | 6 | 6 | 6 | 10 | 10 |
放第三个物品c(6kg,5) | 0 | ||||||||||
放第四个物品d(2kg,3) | 0 | ||||||||||
放第五个物品入e(2kg,6) | 0 |
开始放入c物品:
放c(6kg,5)物品,道理同放b一样,0<k<6是,与同行的上一排相同,k=6时,不放c的价值和同列上一行相同,放c时先回到放c之前的状态f[2,0],价值是f[2,0]+5=5<6,此时选择不放c。当k=11时,不放c的价值是10,放c的价值是f[2,4]+5=11>10,所以选择把c放进去。
0kg | 1kg | 2kg | 3kg | 4kg | 5kg | 6kg | 7kg | 8kg | 9kg | 10kg | |
不放物品 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
放第一个物品a(4kg,6) | 0 | 0 | 0 | 0 | 6 | 6 | 6 | 6 | 6 | 6 | 6 |
放第二个物品b(5kg,4) | 0 | 0 | 0 | 0 | 6 | 6 | 6 | 6 | 6 | 10 | 10 |
放第三个物品c(6kg,5) | 0 | 0 | 0 | 0 | 6 | 6 | 6 | 6 | 6 | 10 | 11 |
放第四个物品d(2kg,3) | 0 | ||||||||||
放第五个物品入e(2kg,6) | 0 |
放入d、e绘出表格:
0kg | 1kg | 2kg | 3kg | 4kg | 5kg | 6kg | 7kg | 8kg | 9kg | 10kg | |
不放物品 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
放第一个物品a(4kg,6) | 0 | 0 | 0 | 0 | 6 | 6 | 6 | 6 | 6 | 6 | 6 |
放第二个物品b(5kg,4) | 0 | 0 | 0 | 0 | 6 | 6 | 6 | 6 | 6 | 10 | 10 |
放第三个物品c(6kg,5) | 0 | 0 | 0 | 0 | 6 | 6 | 6 | 6 | 6 | 10 | 11 |
放第四个物品d(2kg,3) | 0 | 0 | 3 | 3 | 6 | 6 | 9 | 9 | 9 | 10 | 11 |
放第五个物品入e(2kg,6) | 0 | 0 | 6 | 6 | 9 | 9 | 12 | 12 | 15 | 15 | 15 |
精华总结:
不难得出这样的公式(这是核心,只要理解了公式,写程序会迎刃而解,上面的废话也不用再看了)
f[i,k]= 0(i=0或k=0) //背包不能装东西,或者没有东西可以装
f[i,k]= f[i-1,k] (k<Wi) //Wi表示第i个物品的重量
f[i,k]= max{f[i-1,k],f[i-1,k-Wi]+pi} (k>=Wi) //pi表示第i个物品的价值
这年头做个小偷真难啊,还得算那么多东西!
下面是java代码:
import java.util.ArrayList;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入背包能容纳的重量:");
int w = scanner.nextInt();
System.out.println("请输入物品件数:");
int n = scanner.nextInt();
int[][] tab = new int[n + 1][w + 1];// 结果表
int[][] a = new int[n][2];// 物品的重量价值记录笔
System.out.println("请分别输入" + n + "件物品的重量和价值:");
for (int i = 0; i < n; i++) {
for (int k = 0; k < 2; k++) {
a[i][k] = scanner.nextInt();
}
}
System.out.println(n + "个物品的重量、价值分别是:");
for (int i = 0; i < n; i++) {
System.out.print("(" + a[i][0] + "kg," + a[i][1] + ") ");
}
System.out.println();
for (int i = 1; i < n + 1; i++) {
for (int k = 1; k < w + 1; k++) {
if (k < a[i - 1][0]) {
tab[i][k] = tab[i - 1][k];
} else {
tab[i][k] = max(tab[i - 1][k - a[i - 1][0]] + a[i - 1][1], tab[i - 1][k]);
}
}
}
System.out.println("绘制的二维表格:");
for (int i = 0; i < n + 1; i++) {
for (int k = 0; k < w + 1; k++) {
System.out.print(tab[i][k] + " ");
}
System.out.println();
}
}
private static int max(int a, int b) {
return a > b ? a : b;
}
}