一、0/1背包的概念
有n个物品,它们有各自的价值和体积,现有给定容量的背包,如何让背包装入的物品的价值最大?
如:假设你的背包容量为10,物品的重量和价值如下,且每个物品只有一个,怎么样拿才能使你背包的价值最大呢?
编号: 1 2 3 4 物品重量: 2 3 5 5 物品价值: 2 4 3 7 用
xi
代表第i个珠宝的选择(xi = 1
代表选择该珠宝,0则代表不选),vi
代表第i个珠宝的价值,wi
代表第i个珠宝的重量。于是我们就有了这样的限制条件:
(背包容量)
2x1+3x2+5x3+5x4≤10
二、解决思路
初始状态是背包容量为10,背包内物品总价值为0
①对于1号珠宝,当前容量为10,容纳它的重量2绰绰有余,因此有两种选择,选它或者不选
② 继续进行选择,如果我们选择了珠宝1,那么对于珠宝2,当前剩余容量为8,大于珠宝2的容量3,因此也有两种选择,选或者不选。
③最后如图
本来应该有16个待选结果,但有三个结果由于容量不足以容纳下最后一个珠宝,所以就没有继续进行裂变。
然后,我们从这些结果中,找出价值最大的那个,也就是
13
,这就是我们的最优选择,根据这个选择,依次找到它的所有路径,便可以知道该选哪几个珠宝,最终结果是:珠宝4,珠宝2,珠宝1。
三、递推关系
定义函数KS(i,j):代表当前背包剩余容量为j时,前i个物品最佳组合所对应的价值;
- 背包剩余容量不足以容纳该物品,此时背包的价值与前i-1个物品的价值是一样的,KS(i,j) = KS(i-1,j)
- 背包剩余容量可以装下该商品,此时需要进行判断,因为装了该商品不一定能使最终组合达到最大价值,如果不装该商品,则价值为:KS(i-1,j),如果装了该商品,则价值为KS(i-1,j-wi) + vi,从两者中选择较大的那个,所以就得出了递推关系式:
四、实现代码
1.递归
这里为了方便处理,将数组ws和vs都增加了一个补位数0,防止数组越界,输出结果
public class Solution{
int[] vs = {0,2,4,3,7};
int[] ws = {0,2,3,5,5};
@Test
public void testKnapsack1() {
int result = ks(4,10);
System.out.println(result);
}
private int ks(int i, int c){
int result = 0;
if (i == 0 || c == 0){
// 初始条件
result = 0;
} else if(ws[i] > c){
// 装不下该珠宝
result = ks(i-1, c);
} else {
// 可以装下
int tmp1 = ks(i-1, c);
int tmp2 = ks(i-1, c-ws[i]) + vs[i];
result = Math.max(tmp1, tmp2);
}
return result;
}
}
2.动态规划
当i=1时,即只有珠宝1可供选择,那么如果容量足够的话,最大价值自然就是珠宝1的价值了。
当i=2时,有两个物品可供选择,此时应用上面的递推关系式进行判断即可。这里以i=2,j=3为例进行分析:
最终表格
这样,我们就得到了最后的结果:13。根据结果,我们可以反向找出各个物品的选择,寻找的方法很简单,就是从
i=4,j=10
开始寻找,如果ks(i-1,j)=ks(i,j)
,说明第i个物品没有被选中,从ks(i-1,j)
继续寻找。否则,表示第i个物品已被选中,则从ks(i-1,j-wi)
开始寻找。
代码:
public class Solution{
int[] vs = {0,2,4,3,7};
int[] ws = {0,2,3,5,5};
Integer[][] results = new Integer[5][11];
@Test
public void testKnapsack3() {
int result = ks3(4,10);
System.out.println(result);
}
private int ks3(int i, int j){
// 初始化
for (int m = 0; m <= i; m++){
results[m][0] = 0;
}
for (int m = 0; m <= j; m++){
results[0][m] = 0;
}
// 开始填表
for (int m = 1; m <= i; m++){
for (int n = 1; n <= j; n++){
if (n < ws[m]){
// 装不进去
results[m][n] = results[m-1][n];
} else {
// 容量足够
if (results[m-1][n] > results[m-1][n-ws[m]] + vs[m]){
// 不装该珠宝,最优价值更大
results[m][n] = results[m-1][n];
} else {
results[m][n] = results[m-1][n-ws[m]] + vs[m];
}
}
}
}
return results[i][j];
}
}
class Solution{
public static void main(String[] args) {
int[] v = {2,4,3,7};
int[] w = {2,3,5,5};
int capaticy =10;
int dp[][]=new int[v.length+1][capaticy+1];
for (int i = 1; i < v.length+1; i++) { //物品的个数
for (int j = 0; j < capaticy+1; j++) { //容量
if (w[i-1]>j){
//装不下,为什么是w[i-1]而不是w[i],是因为我用i代表物品的个数,i=0时,表示没有物品可选择,自然数组都为0
// i=1时,表示存在一个物品,对应数组的下标0,所以是w[i-1]
dp[i][j]=dp[i-1][j];
}else{
dp[i][j]=Math.max(dp[i-1][j],dp[i-1][j-w[i-1]]+v[i-1]);
}
}
}
for (int i = 0; i <v.length+1 ; i++) {
for (int j = 0; j <capaticy+1 ; j++) {
System.out.print(dp[i][j]+" ");
}
System.out.println();
}
}
}