1. 程式人生 > >【動態規劃】一次搞定三種揹包問題

【動態規劃】一次搞定三種揹包問題

前文連結

【動態規劃】01揹包問題

【動態規劃】01揹包問題【續】

【動態規劃】完全揹包問題

【動態規劃】多重揹包問題

說明

看完前面四篇關於揹包問題的文章,你會發現揹包問題其實也不過如此,而且它們之間有很多相似的地方,本篇文章就來揭開它們面紗,將揹包問題徹底搞定。

三種揹包問題的比較

先來回顧一下三個揹包問題的定義:

01揹包:
有N件物品和一個容量為V的揹包,第i件物品消耗的容量為Ci,價值為Wi,求解放入哪些物品可以使得揹包中總價值最大。

完全揹包:
有N種物品和一個容量為V的揹包,每種物品都有無限件可用,第i件物品消耗的容量為Ci,價值為Wi,求解放入哪些物品可以使得揹包中總價值最大。

多重揹包:
有N種物品和一個容量為V的揹包,第i種物品最多有Mi件可用,每件物品消耗的容量為Ci,價值為Wi,求解入哪些物品可以使得揹包中總價值最大。

三種揹包問題都有一個共同的限制,那就是揹包容量,揹包的容量是有限的,這便限制了物品的選擇,而三種揹包問題的共同目的,便是讓揹包中的物品價值最大。

不同的地方在於物品數量的限制,01揹包問題中,每種物品只有一個,對於每種物品而言,便只有選和不選兩個選擇。完全揹包問題中,每種物品有無限多個,所以可選的範圍要大很多。在多重揹包問題中,每種物品都有各自的數量限制。

三種揹包問題雖然對於物品數量的限制不一樣,但都可以轉化為01揹包問題來進行思考。在完全揹包問題中,雖然每種物品都可以選擇無限個,但由於揹包容量有限,實際上每種物品可以選擇的數量也是有限的,那麼將每種物品都看做是 V/Ci 種只有一件的不同物品,不就成了01揹包問題嗎?對於多重揹包也是如此,只是每種物品的膨脹數量變成了 min{Mi, V/Ci}。

所以說,01揹包問題是所有揹包問題的基礎,弄懂了01揹包問題後,完全揹包和多重揹包就沒有什麼難的地方了。

下面我們來對比一下三種揹包問題的狀態轉移方程,以便更好的理解它們之間的聯絡:

01揹包的狀態轉移方程:

F[i,v] = max{F[i-1,v], F[i-1,v-Ci] + Wi}

完全揹包的狀態轉移方程:

F[i,v] = max{F[i-1,v-kCi] + kWi | 0 <= kCi <= v}

多重揹包的狀態轉移方程:

F[i,v] = max{F[i-1,v-kCi] + kWi | 0 <= k <= Mi}

把這三個方程放到一起,便能很清晰的看到它們之間的關係了,三種揹包問題都是基於子問題來選取價值最大的一個,只是選擇的範圍不一樣。

01揹包考慮的是選和不選,所有隻需要比較兩種策略的最大值即可,而完全揹包和多重揹包要考慮的是選幾個的問題。

這樣說也許還是不夠形象,舉個栗子就能比較好的說明了:

假設揹包容量為10,有兩個物品可選,價值分別為:3,2,容量佔用分別為,4,3。

初始狀態:

01揹包的填表法:

完全揹包的填表法:

多重揹包的填表法:

假設兩種物品的可選數量分別為:2,1.

下面再來看看三種揹包問題的一維陣列解決方案。

01揹包:

for i <- 1 to N
    for v <- V to Ci
        F[v] = max{F[v],F[v-Ci] + Wi}

將其核心部分抽象出來:

def ZeroOneKnapsack(F,C,W)
    for v <- V to C
        F[v] = max{F[v],F[v-C] + W}

則01揹包問題可以表示為:

for i <- 1 to N
    ZeroOneKnapsack(F,Ci,Wi)

N代表物品數量,Ci代表第i個物品佔用的容量,V代表揹包總容量,Wi代表第i個物品的價值,下同。

完全揹包:

for i <- 1 to N
    for v <- Ci to V
        F[v] = max{F[v],F[v-Ci] + Wi}

將其核心部分抽象出來:

def CompleteKnapsack(F,C,W)
    for v <- C to V
        F[v] = max{F[v],F[v-C] + W}

則完全揹包問題的解可以表示為:

for i <- 1 to N
    CompleteKnapsack(F,Ci,Wi)

多重揹包:

for i <- 1 to N
    if v < Ci * Mi
        F[v] = max{F[v],F[v-Ci] + Wi}
    else
        for v <- Ci to V
            k <- 1
            while k < M && v > Ci * k
                F[v] = max{F[v],F[v-Ci*k] + Wi*k}
                k++

抽象出核心邏輯:

def MultiKnapsack(F,C,W,M)
    if C * M >= V
        CompleteKnapsack(F,C,W)
        return
    else
        k <- 1
        while k < M
            ZeroOneKnapsack(F,KC,KW)
            k++
        return

則多重揹包問題的解可以表示為:

for i <- 1 to N
    MultiKnapsack(F,Ci,Wi,Mi)

Mi 代表第i件物品最多可選數量

混合揹包問題

現在我們來考慮一種更為複雜的情況,如果可選的物品同時具有上述三種特性,即:有的物品只能選一個,有的物品可以選擇任意多個,有的物品只能選擇有限多個,那麼此時該如何決策呢?

其實有了上面的總結和抽象,這種混合揹包問題就小菜一碟了。

回顧一下上面的三種揹包問題的抽象解,就會發現他們每次都只會考慮一種物品,區別只在於第i個物品的可選策略。所以對於混合揹包問題,同樣也可以一個一個物品考慮,如果這個物品是最多選一個,那麼就採用01揹包的解決策略,如果是可以選擇任意多個,那麼就使用完全揹包的解決策略,如果只能選擇有限多個,那麼就使用多重揹包的解決策略。

虛擬碼如下:

for i <- 1 to N
    if 第i件物品屬於01揹包
        ZeroOneKnapsack(F,Ci,Wi)
    else if 第i件物品屬於完全揹包
        CompleteKnapsack(F,Ci,Wi)
    else if 第i件物品屬於多重揹包
        MultiKnapsack(F,Ci,Wi,Mi)

總結

到此為止,我們就已經比較完美的解決了三種揹包問題,順便還解決了一下混合揹包問題。雖然條件各不相同,但是解題思路卻很相似,相信經過這一篇文章的總結,你對於揹包問題也會有更好的理解,並且領會到這種抽象問題的好處。

當然,更深層次的揹包問題還有很多,比如二維費用問題,物品依賴問題,鑑於博主學疏才淺,暫時也沒有探索的興趣,所以就不一一進行說明了,有興趣的話可以自行搜尋相關內容。

如果本文對你有幫助,不要吝嗇你的點贊哦。也歡迎關注我的公眾號進行留言交流。

文末再贈送一個小福利,關注公眾號並回復: python電子書大全 即可無套路獲得上百本python電子書資源