0-1背包问题的描述:有吉他,音响和电脑三件物品,它们的重量分别是1磅、4磅和3磅,它们的价值分别是1500、3000和2000,现在给你个承重为4磅的背包,如何让背包里装入的物品具有最大的价值总和?
老师给我们这个问题的时候说的是一个小偷入室偷窃,问如何才能使偷到的东西具有最大的价值。这道题是动态规划很典型的问题,也是一个著名的NP难问题。话不多说直接进入正题。
- 思路分析
算法的主要思想是利用动态规划来解决,每次遍历到第i个物品,根据w[i]和v[i]来确定是否需要将该物品放入背包中,即对于给定的n个物品,设w[i]、v[i]分别为第i个物品的重量和价值,C为背包的容量,再令v[i][j]表示在前i个物品中能够装入容量为j的背包中的最大价值。
- 具体实现
我们通过遍历填表来寻找这个算法的规律,当i=1时,代表只有第一个物品–吉他,j表示背包的容量,从1-4,当背包的容量为1时,吉他可以放入,因为i=1,所以物品只有吉他,在背包容量为2、3、4时也只能放入吉他,如图所示:
当i=2时,这时新商品为音响,重量为4磅,背包容量为1、2、3磅时无法放入,所以这时采用上个单元格的解法,当j=4时,背包容量为4,能够放入音响,将它的价值和前一个单元格的最大价值进行比较,装入最大价值的物品。
当i=3时,新物品为电脑,同理当背包容量为1、2时之装入吉他,容量为3时,比较价值然后装入电脑,当背包容量为4时,有两种装法:一种是背包中放吉他和电脑;一种是只放入音响;到底是怎么放入背包中的价值最大呢,我们很容易的就判断出当然是前者的价值最大。但是计算机又如何判断出来的呢?
在这里引入一个规律:
当新放入物品的重量大于背包的容量时,v[i][j]=v[i-1][j] ,就直接使用上一个单元格的装入策略。
当准备加入新增物品的容量小于当前背包的容量时,v[i][j]=max{v[i-1][j],v[i]+v[i-1][j-w[i]]}。代码的意思就是把上一个单元格装入的最大价值和 当(前物品价值+剩余背包空间容量价值) 进行比较,取最大值。
最后一个单元格就是背包装取物品价值的最大值。
- 具体代码
import java.util.*;
public class KnapsackProblem{
public static void main(String[] args){
int[] weight = {1, 4, 3}; // 物品的重量
int[] value = {1500, 3000, 2000}; // 物品的价值
int m =4; // 背包的容量
int n = value.length; // 物品的个数
// 创建二维数组
// v[i][j] 表示在前i个物品中能够装入容量为j的背包中的最大价值
int[][] v = new int[n+1][m+1]; // 因为第一行和第一列要置零 所以容量和数目要加1
//创建一个二维数组path 记录物品装入背包过程
int[][] path = new int[n+1][m+1];
// 初始化第一行和第一列 分别置零
for(int i = 0; i < v.length; i++){
v[i][0] = 0; // 将第一列设置为零
}
for(int j = 0; j < v[0].length; j++){
v[0][j] = 0; // 将第一行设置为零
}
for(int i = 1; i < v.length; i++){
for(int j = 1; j< v[0].length; j++){
if(weight[i-1] > j){
// 当准备新加入的物品的重量大于当前背包的容量时,直接使用上一个单元格的信息;
v[i][j] = v[i-1][j];
}
else{
// 当准备新加入的物品的重量小于当前背包的容量时, 比较(上一个单元格装入的最大值 和 当前商品价值+剩余空间物品价值) 取最大值
if(v[i-1][j] >= value[i-1]+v[i-1][j-weight[i-1]]){
v[i][j] = v[i-1][j];
}
else{
v[i][j] = value[i-1]+v[i-1][j-weight[i-1]];
path[i][j] = 1; // 记录新物品放入情况
}
}
}
}
// 打印输出结果
for(int i=1; i < v.length; i++){
for(int j = 1; j < v[0].length;j++){
System.out.printf("%-6d",v[i][j]);
}
System.out.printf("\n");
}
System.out.printf("物品放入记录:\n");
int i = path.length - 1; // 行的最大下标
int j = path[0].length - 1; // 列的最大下标
while( i > 0 && j > 0 ){
if(path[i][j] == 1){
System.out.println("第" + i + "个物品放入背包中");
j -= weight[i-1];
}
i--;
}
System.out.printf("背包中物品的总价值为:%d\n",v[n][m]);
}
}
这是这个算法的核心思想,只要把这里想明白,这个问题就变得很简单。