背包问题(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));
}

}

java背包问题的递归方法 java背包问题代码_System


运行结果如下:

最大值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);
	}
}

java背包问题的递归方法 java背包问题代码_背包问题_02


运行结果如下:

最大值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);
	}
}

java背包问题的递归方法 java背包问题代码_Java_03


运行结果:

最大值72 ;

dp[N+1][V+1]矩阵

选择第1件,第1件,第3件,第3件,第3件;

如有错误,欢迎指正