動態規劃——揹包問題1:01揹包

揹包問題是動態規劃中的一個經典題型,其實,也比較容易理解。算法

當你理解了揹包問題的思想,凡是考到這種動態規劃,就必定會得很高的分。數組

 

揹包問題主要分爲三種:學習

01揹包    徹底揹包    多重揹包優化

 

其中,01揹包是最基礎的,最簡單的,也是最重要的。spa

由於其餘兩個揹包都是由01揹包演變而來的。因此,學好01揹包,對接下來的學習頗有幫助。code

 

廢話很少說,咱們來看01揹包。blog

 

01 揹包問題:給定 n 種物品和一個容量爲 C 的揹包,物品 i 的重量是 wi,其價值爲 vi 。排序

問:應該如何選擇裝入揹包的物品,使得裝入揹包中的物品的總價值最大?ci

 

第一眼看上去,咱們會想到貪心(揹包問題還不會QAQ)。模板

用貪心算法來看,流程是這樣的:

1.排序,按價值從大到小排序

2.選價值儘量大的物品放入。

 

可是,貪心作這題是錯的。

 

讓咱們舉個反例:

n=5,C=10 重量 價值 第一個物品: 10            5 第二個物品: 1             4 第三個物品: 2             3 第四個物品: 3             2 第五個物品: 4             1

 

用貪心一算。答案是5,但正解是用最後4個,價值總和是10.

 

那將重量排序呢?

其實也不行。

稍微一想就想到了反例。

 

咱們須要藉助別的算法。

貪心法用的是一層循環,而數據不保證在一層循環中得解,因而,咱們要採用二層循環。

這也是揹包的思想之一。

 

來看揹包算法:

1.用二維數組dp [ i ] [ j ],表示在面對第 i 件物品,且揹包容量爲  j 時所能得到的最大價值 

好比說上面的那個反例:

dp [ 1 ] [ 3 ] = 4 + 3 = 7.

2.01揹包之因此叫「01」,就是一個物品只能拿一次,或者不拿。

那咱們就分別來討論拿仍是不拿。

(1)j < w[i] 的狀況,這時候揹包容量不足以放下第 i 件物品,只能選擇不拿

dp [ i ] [ j ] = dp [ i - 1 ] [ j ];

(2)j>=w[i] 的狀況,這時揹包容量能夠放下第 i 件物品,咱們就要考慮拿這件物品是否能獲取更大的價值。

~若是拿取,dp [ i ] [ j ] = dp [ i - 1 ] [ j - w [ i ] ] + v [ i ]。 這裏的dp [ i - 1 ] [ j - w [ i ] ]指的就是考慮了i-1件物品,揹包容量爲 j-w[i] 時的最大價值,也是至關於爲第i件物品騰出了w[i]的空間。

 

~若是不拿,dp [ i ] [ j ] = dp [ i-1 ] [ j ]

到底拿不拿呢?要看拿與不拿那個結果更大了。

 

看,這用到了動態規劃的思想:在求值時會用到以前狀態的結果。

 

咱們就能夠得出狀態轉移方程了。

 

1 if(j>=w[i])
2     dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);
3 else 
4     dp[i][j] = dp[i-1][j];

 

 因而,完整代碼就出來了:

code:

 1 int n,c,w[1001],v[1001];  2 int dp[1001][1001];  3 cin>>n>>c;  4 for(int i=1;i<=n;i++)  5     cin>>w[i]>>v[i];  6 for(int i=1;i<=n;i++) //物品 
 7 {  8     for(int j=1;j<=c;j++)  //從一枚舉到C 
 9  { 10         if(j>=w[i]) 11             dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);  //最大值 
12         else                
13             dp[i][j]=dp[i-1][j]; 14  } 15 } 16 cout<<dp[n][c]<<endl;//n個物品,揹包空間爲c的dp值 

 

那麼,這是二維的01揹包,能夠看出,數組受空間限制,不能開太大,因此,咱們還有一種優化01揹包,只有一維的數組

 

先考慮上面講的基本思路如何實現,確定是有一個主循環i=1..N,每次算出來二維數組dp[ i ] [ 0..V ]的全部值。那麼,若是隻用一個數組dp [ 0..V ],能不能保證第i次循環結束後dp[ v ]中表示的就是咱們定義的狀態dp [ i ] [ v ]呢?dp[ i ][ v ]是由dp[ i-1 ][ v ]和dp[ i-1 ][ v-c[i] ]兩個子問題遞推而來,可否保證在推dp[ i ][ v ]時(也即在第i次主循環中推dp[ v ]時)可以獲得dp[ i-1 ][ v ]和dp[ i-1 ][ v-c[i] ]的值呢?事實上,這要求在每次主循環中咱們以v=V..0的順序推dp[ v ],這樣才能保證推dp[ v ]時dp[ v-c[i] ]保存的是狀態dp[ i-1 ][ v-c[i] ]的值。若是將v的循環順序從上面的逆序改爲順序的話,那麼則成了dp[ i ][ v ]由dp[ i ][ v-c[ i ] ]推知,與本題意不符,但它倒是徹底揹包最簡捷的解決方案,故學習只用一維數組解01揹包問題是十分必要的。

1 for(int i=1;i<=n;i++) 2 { 3     for(int v=c;v>=0;v--) 4  { 5         dp[v]=max(dp[v],dp[v-c[i]]+w[i]); 6  } 7 }

這就是代碼,相比上面的簡潔了許多,既優化了空間,又減小了代碼長度。

 

這就是01揹包問題,其實真沒啥難度,記下模板都能過。

 

上面的講解應該很詳細了,你們多看幾遍,應該是能夠理解的。

咱們下次將講解徹底揹包和多重揹包問題,咱們下次見。

相關文章
相關標籤/搜索