问题描述:
有n个重量和价值分别为wi,vi的物品,从这些物品中挑选出重量不超过W的物品,求所有挑选方案中价值总和的最大值。对于每一个物品都只有选和不选的两种选择;所以称为01背包问题。
递归解法:
首先说明记忆型递归(也叫记录型递归)的特点,记忆型递归主要用于求解子问题有重叠现象的递归;是为了避免将一个子问题多次求解;所以在求解子问题是将子问题的解记录下来,到再一次碰到相同的子问题时不需要再一次求解,直接查询记录的结果,这样可以提高效率。到子问题重叠的次数越多效率提高越快。
再看01背包问题,可以发现它和求集合的子集问题很相似;对于每一个物品都面临选和不选的问题。也就是说每一次选择物品时都会产生两条分支。所以我们可以先用DFS的方法求解01背包问题,然后再DFS的基础上将其改变为记忆型递归。
普通递归代码如下:
public class 记忆型递归解决01背包问题 {
static int[] w = {2,1,3,2};//重量表
static int[] v = {3,2,4,1};//价值表
static int n = 4;//物品数量
static int W = 5;//背包的承重极限
public static void main(String[] args){
System.out.println(dfs(0, W));
}
private static int dfs(int i, int ww) {
if(i==n) return 0;//没有物品选择了
if(ww<=0) return 0;//容量不足了
int v1 = dfs(i+1, ww);//不选当前物品,容量不变
if(ww>=w[i]){
int v2 = v[i] + dfs(i+1, ww-w[i]);//选择当前物品
return Math.max(v1, v2);
}
return v1;
}
}
记忆型递归代码如下:
import java.util.Arrays;
public class 记忆型递归解决01背包问题 {
static int[] w = {2,1,3,2};//重量表
static int[] v = {3,2,4,1};//价值表
static int n = 4;//物品数量
static int W = 5;//背包的承重极限
static int[][] rec = new int[n][W+1];//记录表
public static void main(String[] args){
//Arrays.fill(rec, -1);//将记录表初始化为-1
for(int i=0; i<n; i++){
for(int j=0; j<W+1; j++){
rec[i][j] = -1;
}
}
System.out.println(recdfs(0, W));
}
/*
* i表示从第几个物品开始选,ww表示剩余容量
*/
private static int recdfs(int i, int ww) {
if(i==n) return 0;//没有物品选择了
if(ww<=0) return 0;//容量不足了
if(rec[i][ww]>=0){//计算前先检查是否有记录
return rec[i][ww];
}
int v1 = recdfs(i+1, ww);//不选当前物品,容量不变
if(ww>=w[i]){
int v2 = v[i] + recdfs(i+1, ww-w[i]);//选择当前物品
rec[i][ww] = Math.max(v1, v2);//返回前,先记录子问题的解
return Math.max(v1, v2);
}
rec[i][ww] = v1;//返回前,先记录子问题的解
return v1;
}
}
动态规划解法:
动态规划解法就是建立dp表,行表示可选范围,比如第三行表示可选范围是前三个物品;列表示剩余的容量。首先初始化第一行,之后的每一行都可以根据之前的记录得出结果。
代码如下:
import java.util.Arrays;
public class 动态规划解决01背包问题 {
static int[] w = {2,1,3,2};//重量表
static int[] v = {3,2,4,1};//价值表
static int n = 4;//物品数量
static int W = 5;//背包的承重极限
static int[][] rec = new int[n][W+1];//记录表
public static void main(String[] args){
for(int i=0; i<n; i++){
Arrays.fill(rec[i], -1);//将记录表初始化为-1
}
System.out.println(dp());
}
private static int dp() {
int[][] dp = new int[n][W+1];
//初始化第一行
for(int j=0; j<W+1; j++){
if(j>=w[0]){//剩余重量大于第一个物品的重量
dp[0][j] = v[0];
}else{
dp[0][j] = 0;
}
}
//开始构建dp表
for(int i=1; i<n; i++){
for(int j=0; j<W+1; j++){
if(j>=w[i]){//要得起,剩余容量大于第i个物品的重量
int v1 = v[i] + dp[i-1][j-w[i]];//要
int v2 = dp[i-1][j];//不要
dp[i][j] = Math.max(v1, v2);
}else{//要不起
dp[i][j] = dp[i-1][j];
}
}
}
return dp[n-1][W];
}
}