背包问题描述:
背包的容量为V,现在有N类物品,第i类物品的重量是weight[i],价值是value[i],那么往该背包里装东西,怎样装才能使得最终包内物品的总价值最大。这里装物品主要由三种装法:
1、0-1背包:每类物品最多只能装一次
2、多重背包:每类物品都有个数限制,第i类物品最多可以装num[i]次
3、完全背包:每类物品可以无限次装进包内
采用动态规划方法求解:
用dp[N][V]来存储中间状态值,dp[i][j]表示前i件物品能装入容量为j的物品总价值最大,所以最终的结果值是dp[N][V]。现在需要求状态转移条件。假如现在已经求解出了前i-1件物品装入容量j的背包价值最大,因为固定容量j不变,所以对即将装入的i件物品,有如下讨论:
- 1)如果第i件物品的重量weight[i] > j,表示不能装入,所以dp[i][j] = dp[i-1][j];
- 2)如果第i件物品的重量weight[i] <= j, 择优dp[i][j] = dp[i-1][j-weight[i]]+value[i];
因为是要求最大的价值,所以如果装了第i件物品后的总价值dp[i-1][j-weight[i]]+value[i]>大于之前的总价值dp[i-1][j],则肯是最大的;反之则说明第i件物品不必装入容量为j的背包。状态转移方程就是:
dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-weight[i]]+value[i])
Java代码实现
public static String pack(int N, int V, int[]weight, int[] value){
// 为了方便,我们按照正常的1开始,所以中间变量会多于一个,第0个都是0
int[][]dp = new int[N+1][V+1];
for(int i=1; i<N+1; i++){
for(j=1; j<V+1;j++){
if(weight[i-1] > j){
dp[i][j] = dp[i-1][j];
}else{
dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-weight[i-1]]+value[i-1]);
}
}
int maxValue = dp[N][V];
// 利用反向回溯的方式,得到装入物品的编号
int j = V;
String str = " ";
for(int i = N; i>0;i--){
if(dp[i][j] > dp[i-1][j]){
str = i + " " + str;
j = j - weight[i];
}
if(j == 0){
break;
}
}
return str;
}
}
第二类问题,假设物品的数量是有限,并不是唯一一个,所以问题的解决方式主要在状态转移的时候,考虑数量的限制;
public static String pack(int N, int V, int[]weight, int[] value, int[] nums){
// 为了方便,我们按照正常的1开始,所以中间变量会多于一个,第0个都是0
int[][]dp = new int[N+1][V+1];
for(int i=1; i<N+1; i++){
for(j=1; j<V+1;j++){
if(weight[i-1] > j){
dp[i][j] = dp[i-1][j];
}else{
//考虑物品的件数限制
int vMax = Math.min(nums[i-1], j/weight[i-1]);
for(int k =0; k<= vMax; k++){
dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-k*weight[i-1]]+k*value[i-1]);
}
}
}
int maxValue = dp[N][V];
// 利用反向回溯的方式,得到装入物品的编号
int j = V;
String str = " ";
for(int i = N; i>0;i--){
//若果dp[i][j]>dp[i-1][j],这说明第i件物品是放入背包的
while(dp[i][j] > dp[i-1][j]){
str = i + " " + str;
j = j - weight[i-1];
}
if(j == 0){
break;
}
}
return str;
}
}
完全背包问题:不考虑物品数量的限制,所以相同的物品可以无限制的放入其中,所以状态转移的时候,有变化的是:
f[i][y] = max{f[i-1][y], (f[i][y-weight[i]]+value[i])},
Java实现是:
public static String pack(int N, int V, int[]weight, int[] value){
// 为了方便,我们按照正常的1开始,所以中间变量会多于一个,第0个都是0
int[][]dp = new int[N+1][V+1];
for(int i=1; i<N+1; i++){
for(j=1; j<V+1;j++){
if(weight[i-1] > j){
dp[i][j] = dp[i-1][j];
}else{
// 同一类物品可以多次无限制放入
dp[i][j] = Math.max(dp[i-1][j], dp[i][j-weight[i-1]]+value[i-1]);
}
}
int maxValue = dp[N][V];
// 利用反向回溯的方式,得到装入物品的编号
int j = V;
String str = " ";
for(int i = N; i>0;i--){
//若果dp[i][j]>dp[i-1][j],这说明第i件物品是放入背包的
while(dp[i][j] > dp[i-1][j]){
str = i + " " + str;
j = j - weight[i-1];
}
if(j == 0){
break;
}
}
return str;
}
}