0-1背包和完全背包的差异分析

目录

1、0-1背包

1.1、动态迁移方程

1.2、优优化空间复杂度

1.3、回溯算法解决

1.2、相关习题

2、完全背包

2.1、动态迁移方程

2.2、转换成0-1背包

2.3、优化空间复杂度

2.4、相关习题

3、小结


1、0-1背包

       给你一个可装载重量为 W 的背包和 N 个物品,每个物品有重量和价值两个属性。其中第 i 个物品的重量为 wt[i],价值为 val[i],现在让你用这个背包装物品,最多能装的价值是多少?

             

N = 3, W = 4
wt = [2, 1, 3]
val = [4, 2, 3]

算法返回 6,选择前两件物品装进背包,总重量 3 小于 W,可以获得最大价值 6

1.1、动态迁移方程

        每种物品仅有一件,可以选择放或不放。用子问题定义状态:dp[i][j] 表示前 i 件物品恰放入一个容量为 j 的背包可以获得的最大价值。则其状态转移方程如下:

dp[i][j] = max(dp[i-1][j], dp[i-1][j-wt[i-1]]+val[i-1]);

       注意:物品数组的起始位置,wt[ i-1],val[i-1]表示第 i 件物品的重量和价值。这个方程非常重要,基本上所有跟背包相关的问题的方程都是由它衍生出来的。所以有必要将它详细解释一下:“将前 i 件物品放入容量为 j 的背包中” 这个子问题,若只考虑第 i 件物品的策略(放或不放),那么就可以转化为一个只牵扯前 i−1 件物品的问题。① 如果不放:第 i 件物品,那么问题就转化为“前 i−1件物品放入容量为 j 的背包中”,此时价值为 dp[ i ][ j ] = dp[ i-1 ][  j ];②如果放:第 i 件物品,那么问题就转化为“ 前 i−1 件物品放入剩下的容量为 j−wt[i-1] 的背包中”,此时能获得的最大价值就是 dp[ i-1 ][ j-wt[i-1] ] 再加上通过放入第 i 件物品获得的价值val[i],即dp[i][j] = dp[ i-1 ][ j-wt[i-1] ] + val[i-1]

	public int maxValEx(int[] wt,int[]val,int w){

		int [][]dp = new int[wt.length+1][w+1];
		for(int i = 1; i <= wt.length; i++){
			for(int j = 0; j <= w; j++){
				if(j >= wt[i-1]){
					dp[i][j] = Math.max(dp[i-1][j],dp[i-1][j - wt[i-1]]+val[i-1]);
				}else{
					dp[i][j]=dp[i-1][j];
				}
			}
		}

		return dp[wt.length][w];
	}

时间复杂度:O(N*W),空间复杂度:O(N*W)

1.2、优优化空间复杂度

       以上方法的时间和空间复杂度均为O(N*W),其中时间复杂度已经不能再优化了,但空间复杂度却可以优化到O(W)。上文二维数组实现算法:主循环 i = 1...N,每次算出来二维数组 dp[i][0...W] 的所有值。那么,如果只用一个数组dp[0...V]能不能保证第 i次循环结束后dp[j]中表示的就是我们定义的状态dp[i][j]呢?dp[i][j] 是由 dp[i-1][j] 和 dp[i-1][j - wt[i-1]]+val[i-1] 两个子问题递推而来,能否保证在推 dp[i][j] 时(也即在第i次主循环中推dp[j]时)能够得到dp[i-1][j] 和dp[i-1][j - wt[i-1]]的值呢?事实上,这要求在每次主循环中我们以 j = W...0的顺序推导dp[j],这样才能保证推 dp[j] 时 [j - wt[i-1]] 保存的是状态dp[i-1][j - wt[i-1]]的值。代码如下:

	public int maxValExx(int[] wt,int[]vt,int k){
		int []dp = new int[k+1];
		for(int i = 1; i <= wt.length; i++){
			for(int j = k ; j >=0; j--){
				if(j >= wt[i-1]){
					dp[j] = Math.max(dp[j],dp[j - wt[i-1]]+vt[i-1]);
				}
			}
		}
		return dp[k];
	}

此时的 dp[j] = Math.max(dp[j],dp[j - wt[i-1]]+vt[i-1]) 相当于 dp[i][j] = max(dp[i-1][j], dp[i-1][j-wt[i-1]]+val[i-1]),因为我们是降序遍历包重 计算dp[j] 时 dp[ j - wt[i-1] ] 保存的是上一轮循环的值,如果依然保持升序遍历包重,计算dp[j] 时 dp[ j - wt[i-1] ]已经被本轮循环先计算更新过,此时dp[i][j]依赖的是 dp[ i ][ j - wt[i-1] ],而不在是dp[ i-1 ][ j - wt[i-1] ]。此时状态迁移方程表示的是完全背包而非0-1背包。

继续优化减少迭代次数

	....
	for(int j = k ; j >= wt[i-1]; j--){
		dp[j] = Math.max(dp[j],dp[j - wt[i-1]]+vt[i-1]);
	}
	....

1.3、回溯算法解决

算法:
1、新建一个物品类存储物品重量和价值
2、从物品列表 list 中依次选取物品 i 进包,做出选择,在选择列表中置为物品i为无效值,递归计算,物品进包,包重减少,商品总值增加。
3、依次选取下一件物品,直至选择列表为空,或者包中已经放不下任何一件物品,返回价值为0
4、撤销选择,物品i恢复到选择列表
5、更新本分支下包中物品的总价值

	public  int maxValue(int []wts,int []vals, int W) {
		if(wts.length != vals.length || W <= 0) {
			return 0;
		}
		List<Goods> list = new ArrayList<Goods>();
		for(int i = 0;i < wts.length; i++) {
			list.add(new Goods(wts[i], vals[i]));
		}
		
		return maxValue(list,W);
	}
	

	public int maxValue(List<Goods> list, int W) {
		if(list == null) {
			return 0;
		}

		int maxValue = 0;
		for(int i = 0; i < list.size(); i++) {
			//1、物品已被选;2、物品超重
			if(list.get(i) == null || W < list.get(i).getWeight()) {
				continue;
			}
			
			Goods goods = list.get(i);
			list.set(i,null);//做出选择置为无效
			//放入包中
			int Val = maxValue(list,W-goods.getWeight()) + goods.getValue();
			list.set(i,goods);//撤销选择,恢复物品
			maxValue = Math.max(maxValue, Val);//更新最大值
		}
		return maxValue;
	}


	class Goods{
		int weight;
		int value;
		
		public int getWeight() {
			return weight;
		}
		public int getValue() {
			return value;
		}

		public Goods(int weight, int value) {
			super();
			this.weight = weight;
			this.value = value;
		}
	}

核心代码:

	....
	for(int i = 0; i < list.size(); i++) {
		//1、物品已被选;2、物品超重
		if(list.get(i) == null || W < list.get(i).getWeight()) {
			continue;
		}

		Goods goods = list.get(i);
		list.set(i,null);//做出选择置为无效
		//放入包中
		int Val = maxValue(list,W-goods.getWeight()) + goods.getValue();
		list.set(i,goods);//撤销选择,恢复物品
		maxValue = Math.max(maxValue, Val);//更新最大值
	}
	...

1.2、相关习题

  《0-1背包相关题集》

2、完全背包

2.1、动态迁移方程

       和 0-1背包唯一的的区别就是,每个物品的数量是无限的,也就是从每种物品的角度考虑,与它相关的策略已并非取或不取两种,而是有取 0 件、取 1 件、取 2件……直至取 [ W/wt[i] ] 件等许多种。在前 i - 1种物品的包中,列举出添加0,1... j/wt[i-1]件物品 i 的所有场景,取最大值,状态迁移方程如下:

dp[i][j] = max(dp[i-1][ j- k*wt[i-1] ]+k*val[i-1]) (0<= k <= j/wt[i-1]);

代码如下:

	public int maxVal(int[] wt,int[]val,int w){
		int [][]dp = new int[wt.length+1][w+1];
		for(int i = 1; i <= wt.length; i++){
			for(int j = 0; j <= w; j++){
				int max =0;
				for(int k=0;k <=j/wt[i-1]; k++) {
					max = Math.max(max,dp[i-1][j - k*wt[i-1]]+k*val[i-1]);
				}
				dp[i][j]=max;
			}
		}
		return dp[wt.length][w];
	}

第 i 件物品从0,1,,, j/wt[i-1] 件依次放入,k件物品 i 的放入取决于 k-1件物品 i 的放入。

2.2、转换成0-1背包

上文可知max变量为每次放入1件物品 i 最大价值,因此 dp[i][j] 是依赖上一件物品 i 放入dp[i][j],替换max变量如下:

    ....
	for(int k=0;k <=j/wt[i-1]; k++) {
		dp[i][j] = Math.max(dp[i][j],dp[i-1][j - k*wt[i-1]]+k*val[i-1]);
	}
    ...

此时状态迁移方程为:

dp[i][j] = max(dp[i-1][j],dp[i][j-wt[i-1]]+val[i-1]);

       和0-1不同是当物品 i 放入时 dp[i][j] 依赖的 dp[i][j-wt[i-1]],而不是dp[i-1][j-wt[i-1]], 因为,0-1背包必须保证一件物品只能放一次,当执行物品i放入时,必须保证包中没有物品 i,因此只能从 i-1 件物品中推导;而完全背包则不同,每件物品 i 可以添加无限次,因此,当物品 i 放入时,需要考虑在物品 i 已经在包中的场景即 dp[i][j-wt[i-1]]。

	public int maxVal(int[] wt,int[]val,int w){
		int [][]dp = new int[wt.length+1][w+1];
		for(int i = 1; i <= wt.length; i++){
			for(int j = 0; j <= w; j++){
				if(j>=wt[i-1]) {
					dp[i][j] = Math.max(dp[i-1][j],dp[i][j -wt[i-1]]+val[i-1]);
				}else {
					dp[i][j] = dp[i-1][j];	
				}

			}
		}
		return dp[wt.length][w];
	}

2.3、优化空间复杂度

	public int maxVal(int[] wt,int[]val,int w){
		int []dp = new int[w+1];
		for(int i = 1; i <= wt.length; i++){
			for(int j = 0; j <= w; j++){
				if(j>= wt[i-1]) {
					dp[j] = Math.max(dp[j],dp[j - wt[i-1]]+val[i-1]);
				}
			}
		}
		return dp[w];
	}
     和 0 - 1背包不同的是,也就是遍历包重的顺序为升序,保证当物品 i 放入时,dp[j - wt[i-1]] 已经被本轮循环所更新求出,即物品 i 已经在包中。
 

2.4、相关习题

 
 

3、小结

        0-1背包和完全背包应用的场景最多,差别也较小,当物品只能选择一次则为0-1背包,物品可以选无限次使用为完全背包,二维动态迁移方程,差别在于当物品 i 进包时,计算 dp[ i ][ j ] 依赖的场景,0-1背包依赖  dp[ i-1 ][ j - wt[i-1] ],完全背包则依赖  dp[ i ][j - wt[i-1]],一维动态迁移方程,则完全相同,所不同的是计算dp[i]时,0-1背包则需要保持降序包重遍历for(W...0),而完全背包则需要升序包重遍历for(0...W)。 还有一点需要说明的是,计算二维动态迁移方程时,包重for(0...W)和物品for(i......N)两层循环可以交换,结果正确,而一维动态迁移则不行:1、物品外侧,包重内侧表示组合结果,2:物品内层包重外层表示排列结果。具体参见《完全相关背包题集》
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

老王不让用

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值