1. 程式人生 > 其它 >《揹包九講》 閱讀筆記

《揹包九講》 閱讀筆記

《揹包九講》 閱讀筆記

​ 揹包問題是動態規劃中非常經典的問題,其題目大致描述為:有N件物品和一個容量為V的揹包,第 i 件物品的體積為 c[i], 價值為 w[i]。求解如何裝入可使揹包中物品價值最大。

一、01揹包

  1. 基本思路:

​ 01揹包表示這N件物品每個都只能拿0次或者1次。01揹包是所有揹包問題的基石。設F[i, v] 表示從前 i 件物品中選擇一些放入容量為 v 揹包所能得到的最大價值,那麼可以得到狀態轉移方程為:

​ 在第 i 件物品體積 c[i] 沒有超過揹包容量 v 的情況下,要考慮的就是第 i 件物品放與不放的情況。若不放就轉化為前 i-1 件物品放入容量 v 揹包所能取得的最大價值;若放就轉化為前 i-1 件物品放入 容量 v - c[i] 揹包所能取得的最大價值加上第 i 件物品價值w[i]。

​ 邊界條件為:

​ 這樣,建立二維陣列F,逐行計算後,F[N, V] 即為所求。

  1. 優化思路:但可以注意到,每次計算的結果僅依賴於上一行的當前列和左側某列兩個元素,並且最終結果是最後一行的最後一個元素,所以只需要建立一個一維陣列,計算每行時從後往前覆蓋,將空間複雜度優化為 O(V),虛擬碼為:

    for i=1..N
        for v=V..0
            f[v]=max{f[v],f[v-c[i]]+w[i]};
    

二、完全揹包

​ 完全揹包表示這 N 個物品每個可以拿無數次。完全揹包有兩種思路:

  1. 基本思路:由於揹包容量的限制,每種物品其實最多拿 n[i] = V / c[i]

    次,將這 n[i] 個物品展開,其實就形成了一個的01揹包,因為01揹包問題本身其實沒有限制 N 件物品必須不同。

    優化:

    ​ a. 每件物品都可以拿多次,那麼我們可以只要那些“物美價廉”的物品,即如果一個物品比其他所有相同體積的物品價值都要高或者同價值但體積最小,就可以將其他物品排除不做考慮。

    ​ b. 第 i 個物品不用展開為 n[i] 個,而是 1 2 4 .. 2^k k = 個體積和價值都為原來 2^k 倍的物品,這樣不管是取多少個,都可以由這些合併而來,於是優化為了一個 的01揹包。

  2. 優化思路:不用轉化為完全揹包,對於第 i 件物品分為一件都不取和至少取一件兩種情況討論,於是狀態轉移方程為

邊界條件與 01 揹包時相同

做空間複雜度優化時,可以注意到二維陣列元素的計算僅依賴於上一行的當前列和當前行的左側某列兩個元素,所以建立一個一維陣列,計算時從前往後覆蓋寫,最後程式碼剛好和01揹包僅在 v 的迴圈順序上相反,虛擬碼如下

for i=1..N
    for v=0..V
    	f[v]=max{f[v],f[v-c[i]]+w[i]};

三、多重揹包

​ 多重揹包限制第 i 件商品最多拿 m[i] 個

  1. 基本思路:同樣是將 m[i] 個物品展開,最後變成一個01揹包問題,同樣可以使用完全揹包中的 1.b 的優化。
  2. 優化思路:對於 m[i] * c[i] >= V 的物品實際上等同於無限,對於這些物品可以不用展開,當作無線揹包物品處理即可,即在內層迴圈時 v 從 0 到 V。

四、混合揹包

​ 混合揹包即有的物品只有一次,有的無限次,有的限制 m[i] 次。

​ 如果是01揹包和完全揹包的混合,那麼處理時只需要對不同型別的物品在記憶體迴圈時分別採用正序和倒序即可,虛擬碼為

for i=1..N
    if 第i件物品是01揹包
        for v=V..0
      		f[v]=max{f[v],f[v-c[i]]+w[i]};
    else if 第i件物品是完全揹包
        for v=0..V
	        f[v]=max{f[v],f[v-c[i]]+w[i]};

​ 如果混入了多重揹包,按照多重揹包的思路處理即可。

五、二維費用揹包

​ 二維費用揹包,即揹包有兩個維度的限制。如重量和容量,第 i 件物品的重量為 h[i],揹包最大承重 B。熟悉了二維揹包也就熟悉了N維揹包。

  1. 基本思路:如果是01揹包限制多了一維,那麼狀態也對應多一維即可,此時狀態轉移方程為:

  2. 優化思路:同樣是對空間複雜度進行優化,使用一個二維陣列儲存狀態。

六、分組揹包

​ 分組揹包是指這 N 件物品被分為 K 組,每個組只能取其中一個物品。

  1. 基本思路:使用 F[k, v] 表示,從前 k 組取物品,揹包容量為 v 情況下揹包所能裝下的最大價值。那麼解決的思路同樣是考慮當前組不取和當前組取一個(需要進行比較,選出可取出的最大價值),狀態轉移方程為:

    邊界條件為:F[0,v] = 0 ,虛擬碼為

    for 所有的組k
        for v=V..0
            for 所有的i屬於組k
            	f[k][v]=max{f[k][v],f[k-1][v],f[k-1][v-c[i]]+w[i]}
    
  2. 優化思路:同樣是在空間複雜度上進行優化,但要額外注意的是使用二維陣列儲存狀態時 v 和 i 的迴圈先後可以調換,而使用一維陣列儲存狀態時迴圈必須是先 v 後 i,原因是對於一個 v 要從該組所有的 i 中選出最優的一個才能進行下一個元素的計算,否則同組之間元素將相互影響,具體程式碼可以參考:分組揹包迴圈順序對結果的影響, 虛擬碼為:

    for 所有的組k
        for v=V..0
            for 所有的i屬於組k
            	f[v]=max{f[v],f[v-c[i]]+w[i]}
    

七、其他

​ 對於有依賴的揹包問題和泛化物品力有未逮,不在此記錄。

​ leetcode的揹包問題:

​ 01揹包:494. 目標和416. 分割等和子集1049. 最後一塊石頭的重量 II

​ 完全揹包:322. 零錢兌換279. 完全平方數518. 零錢兌換 II377. 組合總和 Ⅳ

​ 多維揹包:474. 一和零879. 盈利計劃

​ 分組揹包:1155. 擲骰子的N種方法

參考資料

- [1] 揹包九講第二版
- [2] dd大牛的《揹包九講