动态规划算法-07背包问题进阶
簡介
我們在本專欄之前的文章介紹了基礎(chǔ)的01背包問題及其解題思路,本文我們將講述其拓展題型,也就是完全背包問題和多重背包問題。
01背包問題
首先,我們先來簡單回顧一下經(jīng)典的01背包問題,關(guān)于01背包的詳細(xì)講解可以參考我的博客。
給定NNN件物品和一個容量為WWW的背包,第iii件物品的價值為viv_ivi?,重量為wiw_iwi?,求解哪些物品裝入背包使得這些物品的重量不超過背包容量且價值總和最大。
這是最基礎(chǔ)的背包問題,它的特點(diǎn)是每種物品僅有一件,你只能選或者不選,因此叫做01背包問題。我們定義問題的狀態(tài)f[i,j]f[i, j]f[i,j]表示前iii件物品放入容量為jjj的背包可以獲得的最大價值,顯然其狀態(tài)轉(zhuǎn)移方程如下。
f[i,j]=max{f[i?1,j],f[i?1,j?wi]+vi}f[i, j] = max\{ f[i-1, j], f[i-1, j-w_i] + v_i \} f[i,j]=max{f[i?1,j],f[i?1,j?wi?]+vi?}
這個方程非常重要,幾乎所有的背包問題都可以在這個方程上找到影子。因此我們不妨來分析一下:
將前iii件物品放入容量為jjj的背包中,這個子問題,若只考慮第iii件物品的策略(即選或不選),那么就可以轉(zhuǎn)化為一個只牽扯前i?1i-1i?1件物品的問題。若不選擇第iii件物品,那么問題轉(zhuǎn)化為“前i?1i-1i?1件物品放入容量為jjj的背白中”;若選擇第iii件物品,那么問題轉(zhuǎn)化為“前i?1i-1i?1件物品放入剩余容量為j?wij-w_ij?wi?的背包中”,此時獲得的最大價值為f[i?1][j?wi]f[i-1][j-w_i]f[i?1][j?wi?]加上放入第iii件物品的價值viv_ivi?。當(dāng)然,這兩種情況還需要考慮第iii件物品能否放得進(jìn)背包才可。
完全背包問題
問題描述
完全背包問題描述如下。
有NNN種物品以及一個容量為WWW的背包,每種物品都有無限件,第iii種物品的重量為wiw_iwi?且價值為viv_ivi?。求解將哪些物品放入背包可以使得這些物品的重量不超過背包容量且價值總和最大。
基本思路
這個問題很類似于01背包問題,不同的是每種物品不是一件而是無數(shù)件,也就是說,從每種物品的角度看,對其的策略并非取還是不取兩種,而是取0件、取1件…直至取?W/wi?\lfloor W / w_i \rfloor?W/wi??件。如果按照01背包的思路,記f[i,j]f[i, j]f[i,j]為前iii種物品恰放入容量為jjj的背包中的最大價值,仍舊可以按照每種物品不同的策略寫出狀態(tài)轉(zhuǎn)移方程,如下。
f[i,j]=max{f[i?1,j?kwi]+kvi∣0?kwi?j}f[i, j] = max\{ f[i-1, j-kw_i] + k v_i | 0 \leqslant kw_i \leqslant j \} f[i,j]=max{f[i?1,j?kwi?]+kvi?∣0?kwi??j}
這和01背包一樣有O(WN)O(WN)O(WN)個狀態(tài)需要求解,但是求解每個狀態(tài)的時間不再是常數(shù)級別的了,求解狀態(tài)f[i,j]f[i, j]f[i,j]的時間為O(jwi)O(\frac{j}{w_i})O(wi?j?),總的復(fù)雜度可以認(rèn)為是O(NW∑Wwi)O(NW\sum\frac{W}{w_i})O(NW∑wi?W?),顯然這個復(fù)雜度是比較大的。
這個思路源于01背包問題的基本思路,這說明01背包問題確實(shí)是很重要的,可以推廣到其他類型的背包問題上,但是這個復(fù)雜度是需要改進(jìn)的。
簡單優(yōu)化
完全背包有一個簡單有效的優(yōu)化,具體而言,若兩件物品iii、jjj滿足wi?wjw_i \leqslant w_jwi??wj?且vi?vjv_i \geqslant v_jvi??vj?,那么物品jjj可以直接去掉,無需考慮。 這個策略的正確性是顯而易見的,任何情況下都可以將價值小重量大的jjj換為物美價廉的iii,得到的方案至少不會更差。對于隨機(jī)生成的數(shù)據(jù),這個方法會大大減少物品的件數(shù),加快速度。但是,這種策略并不能改善最壞情況的復(fù)雜度,因為精心設(shè)計的數(shù)據(jù)可能一件物品也去不掉。
這個優(yōu)化的思路可以簡單的O(N2)O(N^2)O(N2)實(shí)現(xiàn)。而且背包問題有種不錯的方法:首先去除費(fèi)用大于背包容量WWW的物品,然后使用類似計數(shù)排序的做法,計算出重量相同的物品中價值最高的是哪個,在O(W+N)O(W+N)O(W+N)的時間內(nèi)可以完成這個優(yōu)化。
問題轉(zhuǎn)化求解
01背包是最基本的背包問題,可以考慮將完全背包問題轉(zhuǎn)化為01背包問題來求解。
最直觀的做法是,考慮到第iii種物品最多選?W/wi?\lfloor W / w_i \rfloor?W/wi??,于是可以將第iii種物品轉(zhuǎn)化為?W/wi?\lfloor W / w_i \rfloor?W/wi??件費(fèi)用及價值均不變的物品,然后求解這個01背包問題。雖然這個簡單粗暴的做法并沒有改進(jìn)時間復(fù)雜度,但是指明了完全背包轉(zhuǎn)化為01背包問題的思路:將一種物品拆分為多個只能選0件或者1件的01背包中的物品。
更加高效的轉(zhuǎn)化方法為:把第iii種物品拆分為重量為wi2kw_i2^kwi?2k、價值為vi2kv_i2^kvi?2k的若干件物品,其中kkk取遍滿足wi2k?Ww_i2^k \leqslant Wwi?2k?W的非負(fù)整數(shù)。這是二進(jìn)制的思想,背后的原因是,不管最優(yōu)策略選擇了幾件第iii種物品,其件數(shù)寫成二進(jìn)制之后,總可以表示為若干個2k2^k2k件物品的和。這樣一來就把每種物品拆分為O(log?W/wi?)O(log\lfloor W / w_i \rfloor)O(log?W/wi??)件物品,是一個較大的改進(jìn)。
O(WN)O(WN)O(WN)的算法
直接看這個算法的偽代碼。
f[0...W] <--- 0 for i <--- 1 to Nfor j <--- w_i to Wf[j] <--- max(f[j], f[j-w_i] + v_i)我們發(fā)現(xiàn)一個有趣的地方,那就是這個偽代碼與01背包問題的偽代碼只有jjj的循環(huán)次序不一樣。為什么這個算法是可行的呢?這里我們不妨回憶一下為什么01背包中對于jjj的遍歷必須要按照遞減的順序進(jìn)行,這其實(shí)是為了保證第iii次循環(huán)的狀態(tài)f[i,j]f[i, j]f[i,j]是由狀態(tài)f[i?1,j?wi]f[i-1,j-w_i]f[i?1,j?wi?]遞推而來。換句話說,這正是為了保證每件物品只選了一次啊,保證在考慮“選入第iii件物品”這件策略時,依據(jù)的時一個絕無已經(jīng)選入第iii件物品的子結(jié)果f[i?1,j?wi]f[i-1, j-w_i]f[i?1,j?wi?]。而現(xiàn)在,完全背包的特點(diǎn)恰好是每種物品可以選擇無數(shù)件,所以在考慮“加選意見第iii種物品”這種策略時,卻正需要一個可能已選入第iii種物品的子結(jié)果f[i,j?wi]f[i, j-w_i]f[i,j?wi?],所以就可以并且必須采用jjj的遞增順序循環(huán)。
當(dāng)然,正常的這個算法可以由另外的思路得出,如將基本思路種求解f[i,j?wi]f[i, j-w_i]f[i,j?wi?]的狀態(tài)轉(zhuǎn)移方程顯式寫出來,代入原方程,會發(fā)現(xiàn)該方程可以等價變?yōu)橄旅娴男问?#xff0c;用一維數(shù)組實(shí)現(xiàn)這個遞推就得到了上述偽代碼。
f[i,j]=max(f[i?1,j],f[i,j?wi]+vi)f[i, j] = max(f[i-1, j], f[i, j-w_i] + v_i) f[i,j]=max(f[i?1,j],f[i,j?wi?]+vi?)
實(shí)例分析
我們以Leetcode 322. 零錢兌換為例,其題目如下。
給你一個整數(shù)數(shù)組 coins ,表示不同面額的硬幣;以及一個整數(shù) amount ,表示總金額。計算并返回可以湊成總金額所需的最少的硬幣個數(shù) 。如果沒有任何一種硬幣組合能組成總金額,返回 -1 。你可以認(rèn)為每種硬幣的數(shù)量是無限的。示例 1:
輸入:coins = [1, 2, 5], amount = 11 輸出:3 解釋:11 = 5 + 5 + 1示例 2:
輸入:coins = [2], amount = 3 輸出:-1示例 3:
輸入:coins = [1], amount = 0 輸出:0示例 4:
輸入:coins = [1], amount = 1 輸出:1示例 5:
輸入:coins = [1], amount = 2 輸出:2這題其實(shí)就是個完全背包問題,轉(zhuǎn)化一下就是,這里的硬幣就是物品,這里的總金額就是背包容量,目標(biāo)為最小的硬幣個數(shù)。不過由于這里不是要求不超過背包容量,而是剛好裝滿背包,因此初始化dp數(shù)組需要使用無窮大來進(jìn)行。
class Solution:def coinChange(self, coins: List[int], amount: int) -> int:dp = [float('inf')] * (amount + 1)dp[0] = 0for i in range(len(coins)):for j in range(coins[i], amount+1):dp[j] = min(dp[j], dp[j-coins[i]] + 1)return dp[-1] if dp[-1] != float('inf') else -1多重背包問題
問題描述
有NNN種物品和一個容量為WWW的背包,第iii種物品最多有mim_imi?件可用,每件重量為wiw_iwi?且價值為viv_ivi?。求解將哪些物品裝入背包可以使得這些物品不超過背包容量且價值總和最大。
基本思路
這道題和完全背包問題是很類似的,遞推方程也只需要略微修改即可。因為對于第iii種物品有mi+1m_i + 1mi?+1種策略:取0件、取1件…取mim_imi?件,令f[i,j]f[i, j]f[i,j]表示前iii種物品恰放入一個容量為jjj的背包的最大價值,則狀態(tài)轉(zhuǎn)移方程如下:
f[i,j]=max{f[i?1,j?k?wi]+k?vi∣0?k?mi}f[i, j] = max \{ f[i-1, j-k*w_i] + k * v_i | 0 \leqslant k \leqslant m_i \} f[i,j]=max{f[i?1,j?k?wi?]+k?vi?∣0?k?mi?}
復(fù)雜度為O(W∑mi)O(W\sum m_i)O(W∑mi?)。
問題轉(zhuǎn)化求解
轉(zhuǎn)化為01背包問題是一個常規(guī)的做法:把第iii種物品換位mim_imi?件01背包中的物品,則得到了物品數(shù)為∑mi\sum m_i∑mi?的01背包問題。直接求解這個問題,復(fù)雜度依然是O(W∑mi)O(W\sum m_i)O(W∑mi?)。我們想要優(yōu)化這個復(fù)雜度,借助二進(jìn)制的思路,我們得到下面的策略:將第iii種物品分成若干件01背包中的物品,其中每件物品有一個系數(shù)。這件物品的重量和價值均是原來的重量和價值乘以這個系數(shù)。令這些系數(shù)分別為111,222,222^222 … 2k?12^{k-1}2k?1, mi?2k+1m_i-2^k + 1mi??2k+1且kkk是滿足mi?2k+1>0m_i-2^k+1 > 0mi??2k+1>0的最大整數(shù)。例如,如果mim_imi?為13,那么k=3k=3k=3,這種最多取13件的物品就被分成系數(shù)分別為1,2,4,6的四件物品。分成的這幾件物品的系數(shù)和為mim_imi?,表明不可能取多于mim_imi?件的第iii種物品。另外,這種方法也能保證對于000…mim_imi?間的每一個整數(shù),均可以用若干個系數(shù)的和表示。
這樣,第iii種物品分成了O(logmi)O(logm_i)O(logmi?)種物品,將原問題轉(zhuǎn)化為了復(fù)雜度O(W∑logmi)O(W\sum logm_i)O(W∑logmi?)的01背包問題,這是很大的改進(jìn),處理一件多重背包中物品的偽代碼如下。
def multiple_pacck(f, w_i, v_i, m_i)if w_i * m_i >= Wcomplete_pack(f, w_i, v_i)return k <-- 1while k < m_izeroone_pack(k*w_i, k*v_i)m_i <-- m_i - kk <--- 2kzeroone_pack(w_i*m_i, v_i * m_i)O(WN)O(WN)O(WN)的算法
當(dāng)問題是“每種有若干件的物品能否填滿給定容量的背包”,只須考慮填滿背包的可行性,不需要考慮每件物品的價值時,多重背包問題同樣有O(WN)O(WN)O(WN)復(fù)雜度的算法。
下面介紹一種較為簡單的O(WN)O(WN)O(WN)解多重背包的算法,它的基本思想是這樣的:設(shè)f[i,j]f[i, j]f[i,j]表示“用了前iii種物品填滿容量為jjj的背包后,最多還剩下幾個第iii種物品可用”,如果f[i,j]=?1f[i, j]=-1f[i,j]=?1則說明這種狀態(tài)不可行,若可行應(yīng)滿足0?f[i,j]?mi0 \leqslant f[i, j] \leqslant m_i0?f[i,j]?mi?。
遞推求f[i,j]f[i, j]f[i,j]得偽代碼如下:
f[0,1... W] < --- -1 f[0, 0] <--- 0 for i <--- 1 to Nfor j <--- 0 to Wif f[i-1, j] >= 0f[i, j] = m_ielsef[i, j] = -1for j <--- 0 to W - w_iif f[i, j] > 0f[i, j+w_i] <--- max{f[i, j+w_i], f[i, j] - 1}最終得f[N][0...W]f[N][0...W]f[N][0...W]就是多重背包可行性問題的答案。
實(shí)例分析
力扣目前我還沒有發(fā)現(xiàn)明顯的多重背包類的題目,后續(xù)我會補(bǔ)充。
總結(jié)
本文介紹了01背包的兩種變種,即完全背包和多重背包,這是非常值得思考的兩種題型,本文的內(nèi)容參考了背包九講,歡迎支持原作者。
總結(jié)
以上是生活随笔為你收集整理的动态规划算法-07背包问题进阶的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ByteTrack实时多目标跟踪
- 下一篇: 算法模板-01背包