zoukankan      html  css  js  c++  java
  • 01背包、完全背包

    01背包

    问题描述

    有n个重量(费用)和价值分别为wi,vi的物品。从这些物品中挑选出总重量(费用)不超过W的物品,求所有挑选方案中价值总和的最大值。

    例子

    n=4
    (w,v)={(2,3),(1,2),(3,4),(2,2)}
    W=5
    输出:7

    1 二维数组版:时间复杂度O(nW) ,空间复杂度O(nW)

    记得以下所有方法首先都要初始化dp数组memset(dp,0,sizeof(dp));
    dp[i][j] :表示从前i个物品中选出总重量不超过j的物品时总价值的最大值。

    int dp[MAX_N+1][MAX_W+1];
    
    void solve(){
    	for(int i=1;i<=n;i++){
    		for(int j=0;j<=W;j++){
    			if(j<w[i]){dp[i][j]=dp[i-1][j];}
    			else{
    				dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);
    			}
    		}
    	}
    	printf("%d
    ",dp[n][W]);
    }
    

    2 一维数组版:时间复杂度O(nW) ,空间复杂度O(W)

    上述二维方法可以通过不断重复利用一个一维数组来实现。因为dp[i][j]只可能与dp[i-1][0]到dp[i-1][j]有关,即只与上一行第一列至本列有关,所以内层循环改为由背包容量到物品重量的顺序,即可使用一维数组。
    第i层循环,dp[j] :含义同上。
    此外,由于少了一维,可省去if(j<w[i]){dp[i][j]=dp[i-1][j];}

    int dp[MAX_W+1]
    
    void solve(){
    	for(int i=1;i<=n;i++){
    		for(int j=W;j>=w[i];j--){
    			dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
    		}
    	}
    	printf("%d
    ",dp[W]);
    }
    

    完全背包

    问题描述

    有n个重量(费用)和价值分别为wi,vi的物品。从这些物品中挑选出总重量(费用)不超过W的物品,求所有挑选方案中价值总和的最大值。在这里,每种物品可以挑选任意多件。

    1 二维数组、三重循环版:时间复杂度O(nW^2),空间复杂度O(nW)

    递推关系:
    dp[0][j]=0
    dp[i][j]=max{dp[i-1][j-kw[i]]+kv[i]|0<=k}
    此外,当j<w[i]时,属于k=0的情况。

    int dp[MAX_N+1][MAX_W+1];
    
    void solve(){
    	for(int i=1;i<=n;i++){
    		for(int j=0;j<=W;j++){
    			for(int k=0;k*w[i]<=j;k++){
    				dp[i][j]=max(dp[i][j],dp[i-1][j-k*w[i]]+k*v[i]);
    			}
    		}
    	}
    	printf("%d
    ",dp[n][W]);
    }
    

    2 二维数组、二重循环版:时间复杂度O(nW),空间复杂度O(nW)

    思路:
    在dp[i][j]的计算中选择k(k>=1)个的情况,与在dp[i][j-w[i]]的计算中选择k-1个的情况是相同的(因为至少必须要选一个物品i)。所以递推关系式可分为不选当前物品,和选至少一个当前物品的情况取最大值。

    所以上面递推关系中:
    dp[i][j]=max{dp[i-1][j-kw[i]]+kv[i]|0<=k}
    =max{dp[i-1][j],dp[i][j-w[i]]+v[i]}

    注意是上式右侧的一项是dp[i-1][j],表示不选当前物品的情况;代码1中是dp[i][j],dp[i][j]表示不断用最大值更新dp[i][j]。故此方法省去了关于k的循环。
    (此外,为什么不会从状态dp[i][j-2w[i]]+2v[i]转移到状态dp[i][j]?,个人认为因为这样表示将结果分为三种情况,选0个,选1个,至少选2两个,就要写成dp[i][j]=max{dp[i-1][j],dp[i][j-w[i]]+v[i],dp[i][j-2w[i]]+2v[i]},是繁琐且有赘余计算的。)

    void solve(){
    	for(int i=1;i<=n;i++){
    		for(int j=0;j<=W;j++){
    			if(j<w[i]){dp[i+1][j]=dp[i][j];}
    			else{
    					dp[i][j]=max(dp[i-1][j],dp[i][j-w[i]]+v[i]);
    				}
    			}
    		}
    	}
    	printf("%d
    ",dp[n][W]);
    }
    

    3 一维数组、二重循环版:时间复杂度O(nW),空间复杂度O(W)

    因为dp[i][j]只可能与dp[i-1][j]和dp[i][0]到dp[i][j]有关,即只与上一行同一列和本行第一列至本列有关,所以内层循环可以仍为物品重量到背包容量的顺序,并改为使用一维数组。此外,由于少了一维,可省去if(j<w[i]){dp[i][j]=dp[i-1][j];}

    int dp[MAX_W+1];
    
    void solve(){
    	for(int i=1;i<=n;i++){
    		for(int j=w[i];j<=W;j++){
    				dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
    			}
    		}
    	}
    	printf("%d
    ",dp[W]);
    }
    

    0-1背包和完全背包对比总结

    使用一维数组,两者的算法只有内层遍历顺序不同,但要非常清楚两者的含义完全不同:代码层面,因为内层遍历方向的不同dp[j]一个指dp[i-1][j],一个指dp[i][j]。含义层面,转移到dp[j]的状态来源完全不不同,具体见上。

    reference

    挑战程序设计竞赛
    https://vincentxwd.github.io/blog/2018/08/11/总结-关于01背包和完全背包/

  • 相关阅读:
    版本控制之GitHub亲手实验总结
    Java的HashMap是如何实现的?
    Junit
    由swap引发的关于按值传递和引用传递的思考与总结
    C++了解free和delete
    GitHub使用教程
    Oracle下SQL学习笔记
    Flappy Bird
    尾递归与Continuation(转载)
    十步完全理解SQL(转载)
  • 原文地址:https://www.cnblogs.com/coding-gaga/p/10358149.html
Copyright © 2011-2022 走看看