背包问题总结(01背包、完全背包、多重背包)

一、01背包问题

你有一个背包,最多能容纳的体积是V。

现在有n个物品,第i个物品的体积为vi​ ,价值为wi。

(1)求这个背包至多能装多大价值的物品?

(2)若背包恰好装满,求至多能装多大价值的物品?

输入描述:

第一行两个整数n和V,表示物品个数和背包体积。

接下来n行,每行两个数vi和wi​,表示第i个物品的体积和价值。

1≤n,V,vi,wi≤10001≤n,V,vi​,wi​≤1000

输出描述:

输出有两行,第一行输出第一问的答案,第二行输出第二问的答案,如果无解请输出0。

二维dp解法:

解题思路:动态规划

定义一个二维dp数组,dp[i][j]的值表示当前背包内最大的价值(重量),i表示考虑装入的物品,j表示背包内还剩余容量/体积。初始化为dp[0][0]=0,因为此时没有考虑物品,并且背包不存在容量,因此值为0;

对于每个物品i,我们有两种选择:放入背包,或者不放入背包。

1、如果不放入背包,那么dp[i][j]=dp[i-1][j]。这代码意思是对于现在这件物品i,我们不存入背包中,那么背包剩余的容量还是j,背包包含的价值也同样没有改变。

2、如果放入背包,那么我们需要考虑当前背包的容量j是否可以装下这件物品的体积v[i]。

综合上述两种情况,可以得出

状态转移方程

j<v[i]:dp[i][j] = dp[i-1][j];

j>=v[i]: dp[i][j] = max(dp[i-1][j],dp[i-1][j-v[i]]+w[i]);

遍历过程:

初始化背包dp[0[[0]=0,我们将背包容量从0到V进行遍历,判断是否能够存在每个不同的物品,最后在比较最大值即可。

    public static int knapsack (int V, int n, int[]v,int[]w) {
    	  int[][] dp = new int[n+1][V+1];// i表示装入的物品个数,j表示当前背包容量(体积)
    	  //dp表示的意思 是 n个物品 背包体积 V 能装的重量是dp[i][j]
          dp[0][0] = 0; 
          for (int i = 1; i <=n ; i++) {
              for (int j = 0; j <=V ; j++) {
                    if (j<v[i]){ //背包可用容量小于物品的 容量,无法存入
                        dp[i][j] = dp[i-1][j];
                    }
                    else {
                        dp[i][j] = Math.max(dp[i-1][j],dp[i-1][j-v[i]]+w[i]);
                    }
              }
          }     
          return  dp[n][V];
    }

一维dp解法:

解题思路:

定义dp[j]表示背包容量为j时,能装入的最大价值。

对于每个物品i,考虑是否放入背包中。若不放入,则总价值不变;

若放入,则总价值为f[j-v[i]] + w[i](在当前容量下,放入物品i后的总价值)。

状态转移方程:

dp[j] = max(dp[j], dp[j-v[i]] + w[i])。

区别:

一维dp解法和二维dp解法区别在于我们遍历物品的时候需要从最大的背包容量开始遍历。如果我们从最小背包容量开始考虑,那么在更新较大的背包容量 j 时,较小的背包容量 j-v[i] 可能已经考虑过了物品 i。这会导致物品 i 被错误地计算两次,即它在更新 dp[j-v[i]] 时被考虑过一次,在更新 dp[j] 时又被考虑。而从大容量开始遍历的话就不会存在这种问题

    // 一维dp
    public static int knapsack1 (int V, int n, int[]v,int[]w) {
  	  int[]dp = new int[n+1];
        for (int i = 1; i <=n ; i++) {
            for (int j = V; j >=v[i] ; j++) {
                 dp[j]=Math.max(dp[j], dp[j-v[i]]+w[i]);
            }
        }     
        return  dp[V];
  }

注:对于该题的第二小问,思路是相同的。只需要先初始化整个背包的价值为最小数,依据同样的状态转移方程进行遍历,最后判断这个背包dp[v]是否小于0即可判断是否装满。

        int[] dp2=new int[V+1];
        Arrays.fill(dp2,Integer.MIN_VALUE);
        //没有物品时,价值为0
        dp2[0]=0;
        for(int i=1;i<=n;i++){
            //由于每个物品只能用一次,为了防止重复计算,需要倒序遍历
            for(int j=V;j>=v[i];j--){
                //状态转移,要么选择第i件物品,要么不选,取价值最大的
                dp2[j]=Math.max(dp2[j-v[i]]+w[i],dp2[j]);
            }
        }
        //如果小于0,说明不能从初始状态转移过来,无解
        if(dp2[V]<0){
            dp2[V]=0;
        }
        System.out.println(dp2[V]);

二、完全背包问题

有 N 种物品和一个容量是 V 的背包,每种物品都有无限件可用。

第 i 种物品的体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。输出最大价值。

输入格式:
第一行两个整数,N 和 V,用空格隔开,分别表示物品种数和背包容积。

接下来有 N 行,每行两个整数 vi, wi,用空格隔开,分别表示第 i 种物品的体积和价值。

输出格式:
输出一个整数,表示最大价值。

数据范围:
0 < N, V ≤ 1000
0 < vi, wi ≤ 1000

二维dp解法:

定义二维dp数组,dp[i][j]的含义为:i表示当前考虑装入的物品,j表示背包当前的容量(体积),dp[i][j]的值表示当前背包所含有的价值(重量)。

解题思路与前文01背包是相同的,不过完全背包问题中的 物品不是只有一件,可以多次存储,因此需要多加一重循环,来表示存入该件物品i的数量k。

状态转移方程:

dp[i][j]=max(dp[i-1][j], dp[i-1][j-v[i]*k]+w[i]*k);

循环遍历:

· 外层循环遍历所有物品,中层循环遍历所有可能的背包容量。
· 内层循环遍历每个物品的可能选取次数k(即物品i可以被选取0次、1次、2次,直到k * v[i]超过当前背包容量j)。
· 对于每种情况,更新f[i][j]为max(f[i][j], f[i - 1][j - v[i] * k] + w[i] * k),表示考虑选取k次物品i时的最大价值。

	public static int knapsack (int V, int n, int[]v,int[]w) {
    	  int[][] dp = new int[n+1][V+1];// i表示装入的物品个数,j表示当前背包容量(体积)
    	  //dp表示的意思 是 n个物品 背包体积 V 能装的重量是dp[i][j]
         dp[0][0] = 0; 
         for(int i=1;i<=n;i++){
        	 for(int j=0;j<=V;j++){
        		 for(int k=0;k*v[i]<=V;k++){
        			 dp[i][j]=Math.max(dp[i-1][j], dp[i-1][j-v[i]*k]+w[i]*k);
        		 }
        		
        	 }
         }
          return  dp[n][V];
	  }

一维dp解法:

与解决01背包问题的一维dp解法类似,但是区别在于完全背包问题运行同一物品多次存储,那么就不需要像01背包问题一样进行倒序遍历。直接利用正序遍历的方式,就可以直接将多次存储的问题考虑进去。

状态转移方程:

dp[j]=max(dp[j],dp[j-v[i]]+w[i]);

	public static int knapsack (int V, int n, int[]v,int[]w) {
    	  int[][] dp = new int[n+1][V+1];// i表示装入的物品个数,j表示当前背包容量(体积)
    	  //dp表示的意思 是 n个物品 背包体积 V 能装的重量是dp[i][j]
         dp[0][0] = 0; 
          for(int i=1;i<=n;i++){
        	for(int j=v[i];j<=V;j++){  //这里让j从v[i]开始是为了保证每次开始都是还没有存入这件物品,从0开始也是可以的
                  dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
        		 }
        	}
        	 return dp[V];
	  }

三、多重背包

有 N 种物品和一个容量是 V 的背包。

第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。输出最大价值。

输入格式:
第一行两个整数,N 和 V,用空格隔开,分别表示物品种数和背包容积。

接下来有 N 行,每行三个整数 vi, wi, si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。

输出格式:
输出一个整数,表示最大价值。

数据范围:
0 < N, V ≤ 100
0 < vi, wi, si ≤ 100

解题思路:

完全背包问题是物品有无限多个,而多重背包问题是每个物品i存在一定的数量s[i]。因此做法可以参考完全背包的三重循环做法,只需要将k的范围修改为k<=s[i]。

状态转移方程:

dp[i][j]=max(dp[i][j],dp[i-1][j-k*v[i]]+w[i]*k );        k=0,1,2,3……s[i]

	public static int knapsack (int V, int n, int[]v,int[]w,int[]s) {
  	  int[][] dp = new int[n+1][V+1];// i表示装入的物品个数,j表示当前背包容量(体积)
  	  //dp表示的意思 是 n个物品 背包体积 V 能装的重量是dp[i][j]
       dp[0][0] = 0; 
       for(int i=1;i<=n;i++){
      	 for(int j=0;j<=V;j++){
      		 for(int k=0;k<=s[i] && k*v[i]<=j;k++){
      			 dp[i][j]=Math.max(dp[i][j],dp[i-1][j-k*v[i]]+w[i]*k );
      		 }
      	 }
       }
        return  dp[n][V];
	  }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值