背包問題匯總

來源:互聯網
上載者:User
文章目錄
  • 問題描述
  • 基本思路
  • 最佳化空間複雜度
  • 初始化的細節問題
  • 問題描述
  • 轉換為0-1背包問題
  • 進一步最佳化—O(CN)解法
本文主要來源於《背包問題九講》,我主要選擇了比較簡單的0-1背包問題和完全背包問題進行匯總,並加入了python代碼實現,剛重裝系統,手頭沒有C編譯器,汗。
一、背包問題概述

        背包問題包括0-1背包問題、完全背包問題、部分背包問題等多種變種。其中,最簡單的是部分背包問題,它可以採用貪進法來解決,而其他幾種背包問題往往需要動態規劃來求解。本文對幾種背包問題進行總結,同時給出實現代碼,如有錯誤,請各位大蝦指正。

二、部分背包問題         部分背包問題描述:有N件物品和一個容量為C的背包。第i件物品的重量是w[i],價值是v[i]。求解將哪些物品裝入背包可使價值總和最大。 注意這裡不要求把物品整個裝入,可以只裝入一個物品的部分。         部分背包問題常採用貪心演算法來解決,先對每件物品計算其每單位重量價值v[i]/w[i],然後從具有最大單位價值的物品開始拿,然後拿第二大價值的物品,直到裝滿背包。按照這種貪心策略拿到的必然是價值總和最大。由於該實現代碼簡單,所以此處略去。三、0-1背包問題問題描述        0-1背包問題描述:有N件物品和一個容量為C的背包。第i件物品的重量是w[i],價值是v[i]。求解將哪些物品裝入背包可使價值總和最大。 注意物品只能要麼拿要麼不拿,這也正是0-1的意義所在。可以把部分背包問題看作是拿金粉,而0-1背包問題則是拿金塊,一個可分,一個不可分。基本思路        這是最基礎的背包問題,特點是:每種物品僅有一件,可以選擇放或不放。 用子問題定義狀態:即f[i][w]表示前i件物品恰放入一個容量為c的背包可以獲得的最大價值。則其狀態轉移方程便是: 
f[i][c]=max{f[i-1][c],f[i-1][c-w[i]]+v[i]} 

        這個方程非常重要,基本上所有跟背包相關的問題的方程都是由它衍生出來的。所以有必要將它詳細解釋一下:“將前i件物品放入容量為c的背包中”這個子問題,若只考慮第i件物品的策略(放或不放),那麼就可以轉化為一個只牽扯前i-1件物品的問題。如果不放第i件物品,那麼問題就轉化為“前i-1件物品放入容量為v的背包中”,價值為f[i-1][c];
如果放第i件物品,那麼問題就轉化為“前i-1件物品放入剩下的容量為c-w[i]的背包中”,此時能獲得的最大價值就是f[i-1][c-w[i]]再加上通過放入第i件物品獲得的價值v[i]。最佳化空間複雜度        以上方法的時間和空間複雜度均為O(CN),其中時間複雜度應該已經不能再最佳化了,但空間複雜度卻可以最佳化到O(N)。 由於在計算f[i][c]的時候,我們只需要用到f[i-1][c]和f[i-1][c-w[i]],所以完全可以通過一維數組儲存它們的值,這裡用到的小技巧就是需要從c=C...0開始反推,這樣就能保證在求f[c]的時候f[c-w[i]]儲存的是f[i-1][c-w[i]]的值。注意,這裡不能從c=0...C這樣順推,因為這樣會導致f[c-w[i]]的值是f[i][c-w[i]]而不是f[i-1][c-w[i]。這裡可以最佳化下界,其實只需要從c=C...w[i]即可,可以避免不需要的計算。虛擬碼如下所示:

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

演算法實現的python代碼如下:

def knap01(N, C): //num為物品數目,C為背包容量,w[i]為物品i的重量,v[i]為物品i的價值,f為結果清單    for i in range(0, N):        for c in range(C, w[i]-1, -1):            f[c] = max(f[c], f[c-w[i]] + v[i])

可以使用下面的完整代碼進行測試:

w = [3, 4, 5] //物品重量列表v = [4, 5, 6] //物品價值列表f = []def init(C):  //初始化儲存結果的列表f    del f[:]    for i in range(0, C+1):        f.append(0)    print 0, f    def knap01(N, C): //0-1背包主函數    for i in range(0, N):        for c in range(C, w[i]-1, -1):            f[c] = max(f[c], f[c-w[i]] + v[i])        print i+1, fif __name__ == "__main__":    num, totalCapacity = 3, 10 //分別指定物品數目和背包容量    init(totalCapacity)    knap01(num, totalCapacity)

測試結果如下:

初始化的細節問題

    我們看到的求最優解的背包問題題目中,事實上有兩種不太相同的問法。有的題目要求“恰好裝滿背包”時的最優解,有的題目則並沒有要求必須把背封裝滿。一種區別這兩種問法的實現方法是在初始化的時候有所不同。

   如果是第一種問法,要求恰好裝滿背包,那麼在初始化時除了f[0]為0其它f[1..C]均設為-∞,這樣就可以保證最終得到的f[N]是一種恰好裝滿背包的最優解。如果並沒有要求必須把背封裝滿,而是只希望價格盡量大,初始化時應該將f[0..C]全部設為0。

   為什麼呢?可以這樣理解:初始化的f數組事實上就是在沒有任何物品可以放入背包時的合法狀態。如果要求背包恰好裝滿,那麼此時只有容量為0的背包可能被價值為0的nothing“恰好裝滿”,其它容量的背包均沒有合法的解,屬於未定義的狀態,它們的值就都應該是-∞了。如果背包並非必須被裝滿,那麼任何容量的背包都有一個合法解“什麼都不裝”,這個解的價值為0,所以初始時狀態的值也就全部為0了。


四、完全背包問題問題描述    有N種物品和一個容量為C的背包,每種物品都有無限件可用。第i種物品的重量是w[i],價值是v[i]。求解將哪些物品裝入背包可使這些物品的費用總和不超過背包容量,且價值總和最大。
基本思路

    這個問題非常類似於0-1背包問題,所不同的是每種物品有無限件。也就是從每種物品的角度考慮,與它相關的策略已並非取或不取兩種,而是有取0件、取1件、取2件……等很多種。如果仍然按照解01背包時的思路,令f[i][c]表示前i種物品恰放入一個容量為c的背包的最大權值。仍然可以按照每種物品不同的策略寫出狀態轉移方程,像這樣: 

 f[i][c]=max{f[i-1][c-k*w[i]]+k*w[i]| 0<=k*w[i]<=c }

    這跟0-1背包問題一樣有O(CN)個狀態需要求解,但求解每個狀態的時間已經不是常數了,求解狀態f[i][c]的時間是O(c/w[i]),總的複雜度可以認為是O(CN*Σ(c/w[i])),是比較大的。實現代碼如下:

#####完全背包問題 解法1########def knap_complete(N, C):    for i in range(0, N):        for c in range(C, -1, -1):            for k in range(0, c/w[i]+1):               if k*w[i] <= c:                    f[c] = max(f[c], f[c-k*w[i]] + k*v[i])

使用與0-1背包問題相同的例子,允許程式結果如下(上半段為0-1背包問題輸出,下半段為完全背包問題輸出):

轉換為0-1背包問題

    既然01背包問題是最基本的背包問題,那麼我們可以考慮把完全背包問題轉化為01背包問題來解。最簡單的想法是,考慮到第i種物品最多選C/w[i]件,於是可以把第i種物品轉化為C/w[i]件費用及價值均不變的物品,然後求解這個01背包問題。這樣完全沒有改進基本思路的時間複雜度,但這畢竟給了我們將完全背包問題轉化為01背包問題的思路:將一種物品拆成多件物品。

    更高效的轉化方法是:把第i種物品拆成重量為w[i]*2^k、價值為w[i]*2^k的若干件物品,其中k滿足w[i]*2^k<=C。這是二進位的思想,因為不管最優策略選幾件第i種物品,總可以表示成若干個2^k件物品的和。這樣把每種物品拆成O(log C/w[i])件物品,是一個很大的改進。但我們有更優的O(CN)的演算法。


進一步最佳化—O(CN)解法

我們可以採用與0-1背包問題相反的順序遍曆,從而可以得到O(CN)的解法,虛擬碼如下: 

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

 這個虛擬碼與0-1背包虛擬碼只是c的迴圈次序不同而已。0-1背包之所以要按照v=V..0的逆序來迴圈。這是因為要保證第i次迴圈中的狀態f[i][c]是由狀態f[i-1][c-w[i]]遞推而來。換句話說,這正是為了保證每件物品只選一次,保證在考慮“選入第i件物品”這件策略時,依據的是一個絕無已經選入第i件物品的子結果f[i-1][c-w[i]]。而現在完全背包的特點恰是每種物品可選無限件,所以在考慮“加選一件第i種物品”這種策略時,卻正需要一個可能已選入第i種物品的子結果f[i][c-w[i]],所以就可以並且必須採用c=w[i]..C的順序迴圈。這就是這個簡單的程式為何成立的道理。python實現代碼如下:

#####完全背包問題 解法2########def knap_complete_2(N, C):    for i in range(0, N):        for c in range(w[i], C+1):            f[c] = max(f[c], f[c-w[i]] + v[i])    

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.