背包问题(Java代码实现)
问题描述:
一个背包的总容量为V,现在有N类物品,第i类物品的重量为weight[i],价值为value[i]
那么往该背包里装东西,怎样装才能使得最终包内物品的总价值最大。这里装物品主要由三种装法:
1、0-1背包:每类物品最多只能装一次
2、多重背包:每类物品都有个数限制,第i类物品最多可以装num[i]次
3、完全背包:每类物品可以无限次装进包内
1、0-1背包实现原理及代码
更多原理部分参考:
,
a)求解背包所含物品的最大值:
利用动态规划求最优值的方法。假设用dp[N][V]来存储中间状态值,dp[i][j]表示前i件物品能装入容量为j的背包中的物品价值总和的最大值(注意是最大值),则我们最终只需求知dp[i=N][j=V]的值,即为题目所求。
现在考虑动态规划数组dp[i][j]的状态转移方程:
假设我们已经求出前i-1件物品装入容量j的背包的价值总和最大值为dp[i-1][j],固定容量j的值不变,则对第i件物品的装法讨论如下:
首先第i件物品的重量weight[i]必须小于等于容量j才行,即
1、若weight[i]>j,则第i件物品肯定不能装入容量为j的背包,此时dp[i][j]=dp[i-1][j]
2、若weight[i]<=j,则首先明确的是这件物品是可以装入容量为j的背包的,那么如果我们将该物品装入,则有
dp[i][j]=dp[i-1][j-weight[i]]+value[i]
随之而来的问题是我们要判断第i件物品装到容量为j的背包后,背包内的总价值是否是最大?其实很好判断,即如果装了第i件物品后的总价值dp[i-1][j-weight[i]]+value[i]>没装之前的总价值最大值dp[i-1][j],则肯是最大的;反之则说明第i件物品不必装入容量为j的背包(装了之后总价值反而变小,那么肯定就不需要装嘛)
故,状态转移方程如下:
dp[i][j] = (dp[i-1][j] > (dp[i-1][j-weight[i]]+value[i]))? dp[i-1][j]:(dp[i-1][j-weight[i]]+value[i])
注意:这里的前i件物品是给定次序的
b)求出背包中装入物品的编号
这里我们采用逆推的思路来处理,如果对于dp[i][j]>dp[i-1][j],则说明第i个物品肯定被放入了背包,此时我们再考察dp[i-1][j-weight[i]]的编号就可以了。
代码如下:
public class ZeroOnePack {
/**
* 0-1背包问题
* @param V 背包容量
* @param N 物品种类
* @param weight 物品重量
* @param value 物品价值
* @return
*/
public static String ZeroOnePack(int V,int N,int[] weight,int[] value){
//初始化动态规划数组
int[][] dp = new int[N+1][V+1];
//为了便于理解,将dp[i][0]和dp[0][j]均置为0,从1开始计算
for(int i=1;i<N+1;i++){
for(int j=1;j<V+1;j++){
//如果第i件物品的重量大于背包容量j,则不装入背包
//第i个物品的重量为weight[i],价值为value[i]
if(weight[i] > j)
dp[i][j] = dp[i-1][j];
else
dp[i][j] = Math.max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i]);
}
}
//则容量为V的背包能够装入物品的最大值为
int maxValue = dp[N][V];
System.out.println(maxValue);
//逆推找出装入背包的所有商品的编号
int j=V;
String numStr="";
for(int i=N;i>0;i--){
//若果dp[i][j]>dp[i-1][j],这说明第i件物品是放入背包的
if(dp[i][j]>dp[i-1][j]){
numStr = i+" "+numStr;
j=j-weight[i];
}
if(j==0)
break;
}
//打印dp[][],可以看到最大值就是dp[N][V]
for(int i=0;i<=N;i++) {
for(int K=0;K<=V;K++) {
System.out.printf("%-5d",dp[i][K]);
}
System.out.println();
}
return numStr;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] w= new int[]{ 0 , 2 , 3 , 4 , 5 }; //商品的体积2、3、4、5
int[] v= new int[] { 0 , 3 , 4 , 5 , 6 }; //商品的价值3、4、5、6
int bagV = 8;
System.out.println(ZeroOnePack(bagV,4,w,v));
}
}
运行结果如下:
最大值10
dp[N+1][V+1]矩阵
选择第2件和第4件物品
2、多重背包:每类物品都有个数限制,第i类物品最多可以装num[i]次
由于每个物品多了数目限制,因此初始化和递推公式都需要更改一下。初始化时,只考虑一件物品a时,f[1][j] = min{num[1], j/weight[1]}。 计算考虑i件物品承重限制为y时最大价值f[i][j]时,递推公式考虑两种情况,要么第 i 件物品一件也不放,就是f[i-1][j], 要么第 i 件物品放 k 件,其中 0<= k <=Math.min(num[i],j/weight[i]),
即f[i][j] = max{f[i-1][j], (f[i-1][j-kweight[i]]+kvalue[i])}。 需要判断第 i 件物品的个数是否超过限制数量 num[i]。
public class ManyPack {
public static int manyPack(int V,int N,int[] weight,int[] value,int[] num) {
//初始化动态规划数组
int[][] dp=new int[N+1][V+1];
//将dp[0][j] dp[i][0]设置为0
for(int i=1;i<=N;i++) {
for(int j=1;j<=V;j++) {
if(j>=weight[i])
{
//考虑物品的件数限制
int max=Math.min(num[i],j/weight[i]);
for(int k=0;k<=max;k++)
dp[i][j]=Math.max(dp[i-1][j], dp[i-1][j-k*weight[i]]+k*value[i]);
}else
dp[i][j]=dp[i-1][j];
}
}
int maxValue=dp[N][V];
System.out.println(maxValue);
int j=V;
String numStr="";
for(int i=N;i>0;i--) {
int limit=Math.min(num[i], j/weight[i]);
if(dp[i][j]>dp[i-1][j]) {
int k=limit;
for(k=limit;k>0;k--) {
numStr=i+" "+numStr;
}
j=j-limit*weight[i];
}
if(j==0)
break;
}
//打印dp[][],可以看到最大值就是dp[N][V]
for(int i=0;i<=N;i++) {
for(int K=0;K<=V;K++) {
System.out.printf("%-5d",dp[i][K]);
}
System.out.println();
}
System.out.println(numStr);
return dp[N][V];
}
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] weight= {0,1,2,3};
int[] value= {0,6,13,20};
int[] num= {0,5,3,2};
int bagV=15;
int N=3;
int result=manyPack(bagV,N,weight,value,num);
System.out.println("result="+result);
}
}
运行结果如下:
最大值97
dp[N+1][V+1]矩阵
选择第1件,第1件,第1件, 第2件,第2件,第2件,第3件, 第3件;
result=97再次在主函数里打印最大值
3、完全背包:每类物品可以无限次装进包内
推公式计算时,f[i][j] = max{f[i-1][j], (f[i][j-weight[i]]+value[i])}, 注意这里当考虑放入一个物品 i 时应当考虑还可能继续放入 i,
因此这里是f[i][j-weight[i]]+value[i], 而不是f[i-1][j-weight[i]]+value[i]。
public class CompletePack {
public static int completePack(int V,int N,int[] weight,int[] value,int[] num) {
//初始化动态规划数组
int[][] dp=new int[N+1][V+1];
//将dp[0][j] dp[i][0]设置为0
for(int i=1;i<=N;i++) {
for(int j=1;j<=V;j++) {
if(j>=weight[i])
{
dp[i][j]=Math.max(dp[i-1][j], dp[i][j-weight[i]]+value[i]);
}else
dp[i][j]=dp[i-1][j];
}
}
int maxValue=dp[N][V];
System.out.println(maxValue);
int j=V;
String numStr="";
for(int i=N;i>0;i--) {
while(dp[i][j]>dp[i-1][j]) {
numStr=i+" "+numStr;
j=j-weight[i];
}
if(j==0)
break;
}
//打印dp[][],可以看到最大值就是dp[N][V]
for(int i=0;i<=N;i++) {
for(int K=0;K<=V;K++) {
System.out.printf("%-5d",dp[i][K]);
}
System.out.println();
}
System.out.println(numStr);
return dp[N][V];
}
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] weight= {0,1,2,3};
int[] value= {0,6,10,20};
int[] num= {0,10,5,2};
int bagV=11;
int N=3;
int result=completePack(bagV,N,weight,value,num);
System.out.println("result="+result);
}
}
运行结果:
最大值72 ;
dp[N+1][V+1]矩阵
选择第1件,第1件,第3件,第3件,第3件;
如有错误,欢迎指正