问题描述
给定 n 件物品,物品的重量为 w[i],物品的价值为 v[i]。现挑选物品放入背包中,假定背包能承受的最大重量为 W,问应该如何选择装入背包中的物品,使得装入背包中物品的总价值最大?
今天上课的时候讲到,对于一个01背包,每个物品都可以放或者不放两种状态,突然想到可以使用递归的方法解决这个题,于是有了这篇文章。一般方法为动态规划,在后面进行讲解。
递归实现
思路分析
先上一张图
图中每个节点代表一种状态,每条边代表一种操作
每个节点连接左子树的边为1,连接右子树的边为0:
1代表将该物品放入背包
0代表不将该物品放入背包
两棵左孩子其实就是对同一元素进行不同的操作后得到的新状态
树有一个特点就是:树的孩子还是一棵树。那么这就为我们用递归实现该问题提供了可能.
(1)没有出现叶子结点的情况时( weight 数组和 value 数组最后一个元素还未进行是否放入背包的操作),递归调用该函数,对下一个元素进行操作
(2)当遇到叶子结点(即 weight 数组和 value 数组走到最后的时候),遇到递归出口,开始退栈
代码实现
public class Bag {
public static void main(String[] args) {
int[] weight = new int[]{16,15,15}; //重量
int[] value = new int[]{45,25,25}; //价值
int capacity = 30; //背包容量
int max1 = maxValue(weight, value, 0, capacity, 0, true);
int max2 = maxValue(weight, value, 0, capacity, 0, false);
System.out.println(Math.max(max1, max2));
}
public static int maxValue(int[] weight, int[] value, int i, int surplusCapacity,int nowValue, boolean isPutIn){
if(i >= weight.length){ //递归出口,越界时返回当前价值
return nowValue;
}
//剩余容量大于当前物品的重量
if( isPutIn && surplusCapacity >= weight[i]){
nowValue += value[i]; //更新当前价值
surplusCapacity -= weight[i]; //更新当前容量
}
int max1, max2;
/*假如可以放入,假如的原因是不一定能放入*/
max1 = maxValue(weight, value, i+1, surplusCapacity, nowValue, true);
/*不想放入*/
max2 = maxValue(weight, value, i+1, surplusCapacity, nowValue, false);
return Math.max(max1,max2);
}
}
动态规划实现
思路分析
我们先来考虑一个由前 i 个物品(1 ≤ i ≤ n)定义的实例,物品的重量分别为 w 1 , . . . w i w_1,...w_i w1,...wi,价值分别为 v 1 , . . . v i v_1,...v_i v1,...vi,背包的重量为 j (1 ≤ j ≤ W)。设dp(i, j)为该实例的最优解的物品总价值,也就是说,是能够放进承重量为 j 的背包中的前 i 个物品中最有价值子集的总价值。可以把前 i 个物品中能够放进承重量为 j 的背包中的子集分成两类; 包括第 i 个物品的子集和不包括第 i 个物品的子集
(1)在不包括第 i 个物品的子集中, 最优子集的价值是dp(i - 1,j)
(2)在包括第 i 个物品的子集中(此时必须满足: j − w i j - w_i j−wi ≥ 0),最优子集是由该物品的前i - 1个物品中能够放进承重量为 j − w i j - w_i j−wi的背包的最优子集组成。这种最优子集的总价值为 v i + d p ( i − 1 , j − w i ) v_i + dp(i-1,j-w_i) vi+dp(i−1,j−wi)。
状态转移方程如下:
d
p
(
i
,
j
)
=
{
m
a
x
{
d
p
(
i
−
1
,
j
)
,
v
i
+
d
p
(
i
−
1
,
j
−
w
i
)
}
,
j
−
w
i
≥
0
d
p
(
i
−
1
,
j
)
,
j
−
w
i
<
0
dp(i ,j) = \begin{cases} max\{dp(i-1,j),v_i + dp(i-1,j-w_i)\},& \text{$j-w_i≥0$}\\ dp(i-1,j),& \text{$j-w_i < 0$} \end{cases}
dp(i,j)={max{dp(i−1,j),vi+dp(i−1,j−wi)},dp(i−1,j),j−wi≥0j−wi<0
代码实现
import static java.lang.Integer.max;
public class Bag {
public static void main(String[] args) {
int result = maxValue(new int[]{16,15,15},new int[]{45,25,25},30);
System.out.println(result);
}
public static int maxValue(int[] weight, int[] value,int capacity){
int n = weight.length; //物品个数
int [][]dp = new int[n+1][capacity+1];
dp[0][0] = 0;
for(int i = 1; i < n+1; i++){
for(int j = 1; j < capacity+1; j++){
if( j - weight[i-1] >= 0 ){ //可以放得下当前物品
dp[i][j] = max(dp[i-1][j],value[i-1]+dp[i-1][j-weight[i-1]]);
}else{
dp[i][j] = dp[i-1][j];
}
}
}
return dp[n][capacity];
}
}
关于背包问题的动态规划还有很多的优化处理,我这里只记录下最基本的一种。
下面的背包九讲,对背包问题有非常详细的讲解
背包九讲
欢迎下载