1. 程式人生 > >動態規劃--0,1揹包問題(再也不怕類似揹包問題了)

動態規劃--0,1揹包問題(再也不怕類似揹包問題了)

這種型別問題三大要素:總重量、每件物品重量、每件物品價值,問最終能夠塞進揹包中的價值最大是多少?應該怎麼選擇物品?

當然也不一定是這些,例如上節所說的礦工挖礦:總人數、挖每座礦的人數、每座礦的金子數。

也就是說,只要出現了這三大要素,都可以視為0,1揹包問題(物品不可拆分)

動態規劃三要素:邊界、最優子結構、狀態轉移方程。

我們一步步進行解析:

初始化:物品總重量:c=8,物品類別:n=['a','b','c','d'],物品重量:w=[2,4,5,3],物品價值:v=[5,4,6,2]

假設我們目前只有一個物品a,

  • 揹包的總重量為0,那麼我們獲得總價值為0
  • 揹包的總重量為1,那麼我們獲得總價值為1
  • 揹包的總重量為2,此時,正好可以放下物品a,因為它的重量正好是2,那麼我們獲得總價值為5
  • 在這之後,我們可以獲得的總價值均為5,因為總重量>2,且只有a一個物品

假設我們現在多了一個物品b,

  • 揹包總重量0,那麼我們獲得總價值為0
  • 揹包總重量1,那麼我們獲得總價值為0
  • 揹包總重量2,此時可以放進a,那麼我們獲得總價值為5
  • 揹包總重量3,仍只能放進a,那麼我們獲得總價值為5
  • 揹包總重量4,此時我們既可以放進a,也可以放進b,選價值最大的,也就是放進a,那麼我們獲得總價值為5
  • 揹包總重量5,那麼我們獲得總價值為5
  • 揹包總重量6,此時就可以放進a,b了,那麼我們獲得總價值為5+4=9
  • 在這之後,我們可以獲得的總價值均為9

假設我們現在多了一個物品c

  • 揹包總重量0,那麼我們獲得總價值為0
  • 揹包總重量1,那麼我們獲得總價值為0
  • 揹包總重量2,此時可以放進a,那麼我們獲得總價值為5
  • 揹包總重量3,仍只能放進a,那麼我們獲得總價值為5
  • 揹包總重量4,此時我們既可以放進a,也可以放進b,選價值最大的,也就是放進a,那麼我們獲得總價值為5
  • 揹包總重量5,此時我們可以放a,也可以放c,選最大的,也就是放進c,此時我們獲得總價值為6
  • 揹包總重量6,此時可以放進a,b了,也可以只放進c,選最大的,那麼我們獲得總價值為5+4=9>6
  • 在這之後,我們可以獲得的總價值均為9

依此類推下去,看起來挺複雜,其實是有套路的,那我們應該如何實現。

對付這種問題,一般就直接初始化一個數組:dp[len(n)+1][c+1],即5行9列的二維陣列(行代表物品種類,列代表總重量,多加一列和一行是為了更容易理解)

接下來,我們就從程式碼中一步步剖析:

n=['a','b','b','d']
c=8
w=[2,4,5,3]
v=[5,4,6,2]
def bag(c,w,v):
    #初始化陣列,dp[i][j]表示總重量為j,物品種類為i,可以獲得的最大價值
    dp = [[0 for _ in range(c+1)] for _ in range(len(w)+1)]
    #定義邊界,也就是當我們只有物品a,總重量依次由0-8
    #也就是第一步我們所解釋的
    for i in range(1,c+1):
        if i>=w[0]:
            dp[1][i] = v[0]
    #遍歷從第二行第一列開始
    for i in range(2,len(w)+1):
        for j in range(1,c+1):
            #如果對於第i個物品,當前總重量放不下它,那麼獲得的最大值就是放下之前的i-1個
            if j<w[i-1]:
                dp[i][j] = dp[i-1][j]
            #如果放得下,那麼獲得的最大值就是max(放下之前的i-1個,第i個物品的價值+
            # (總重量-第i個物品的重量)在前i-1個物品的值)
            #注意下標,第i個物品的重量是w[i-1],價值是v[i-1]
            else:
                dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i-1]]+v[i-1])
    return dp
def show(c,w,dp):
    print("最大價值為:",dp[len(w)][c])
    x = [False for _ in range(len(w)+1)]
    j = c #8
    i = len(w) #4
    while i>=0:
        if dp[i][j]>dp[i-1][j]:
            x[i]=True
            j=j-w[i-1]
        i-=1
    print("選擇的物品是:")
    for i in range(len(w)+1):
        if x[i]:
            print("第",i,"個",end='')
    print('')
dp = bag(c,w,v)
for i in range(len(w)+1):
    print(dp[i])
show(c,w,dp)

執行結果:

 

最後在輸出第幾個物品的時候採用由下往上,如果下面的值大於上面的值,說明這個物品被放置了,然後總重量減去該物品重量,繼續判斷,如藍色所標記的。

總結:揹包問題三步走:

(1)初始化dp陣列,行為物品個數+1,列為總重量+1

(2)初始化邊界,只放一個物品,在不同總重量下得到的價值

(3)遍歷陣列,依賴dp[i-1]更新d