咱們在上一篇文章初識動態規劃已經對動態規劃的算法思想有了必定的瞭解,今天咱們再來經過一個經典問題:0,1揹包問題,從更深層次的角度來認識一下動態規劃算法。建議先看上一篇文章,再來看這篇。python
首先,咱們來看一下什麼是0,1揹包問題。算法
問題描述:
給定 n 件物品,物品的重量分別爲w一、w二、w3....,現須要挑選物品放入揹包中,
假定揹包能承受的最大重量爲V,問應該如何選擇裝入揹包中的物品,使得裝入揹包
中物品的的重量最大?數組
首先,咱們最直觀的想法就是,窮舉全部可能的裝法,而後從中選出知足條件的最大值。咱們可使用回溯算法來實現。以下所示:blog
class BagQ: #假設物品的重量都大於0 #揹包的最大承重也大於0 maxW=0 weight=[2,2,8,3,5,3] #物品重量 n=6 #物品個數 w=10 #揹包的最大承重 def getMax(self,i,cw): if cw==self.w or i==self.n: #揹包裝滿或者物品被考察完了 if cw>self.maxW: self.maxW=cw return self.getMax(i+1,cw) #第i個物品不放入揹包 #考察放入第i個物品後,會不會超過揹包的容量 if cw+self.weight[i]<=self.w: self.getMax(i+1,cw+self.weight[i]) #選擇裝第i個物品 bag=BagQ() bag.getMax(0,0) print(bag.maxW)
咱們經過代碼能夠看到回溯算法的時間複雜度較高,是指數級別的。那有什麼方法能夠下降時間複雜度嗎?咱們最好的方式就是把遞歸調用樹畫出來,來找找規律。遞歸調用樹以下所示:遞歸
遞歸樹的每一個節點表示一種狀態,用(i,w)來表示。好比f(1,2)表示第一個物品放入揹包,此時揹包的重量爲2,下一步f(2,2)表示第二個物品不放入揹包,此時揹包的重量不變。而f(2,4)表示第二個物品放入揹包,此時揹包的重量爲4。get
從上圖咱們能夠發現,會有重複的子問題出現,好比f(2,2)被計算了兩次,那咱們該如何避免重複計算呢?it
咱們能夠這麼來看,咱們把整個求解階段分爲n個階段,每一個階段去決策一個物品是否放入揹包。每一個物品決策完以後,對應的揹包中物品的重量會有多種可能,也就是多種狀態。class
咱們來一步一步分析。方法
第一個物品的重量爲2,咱們先來決策第一個物品是否放入揹包,它有兩種可能,要麼放入,要麼不放,與之相對應的揹包的重量也有兩種可能,要麼是0,要麼是2。im
第二個物品的重量爲2,咱們再來決策第二個物品是否放入揹包,它也有兩種可能,要麼放入,要麼不放,與之相對應的揹包的重量就不是兩種可能了,它有4種可能(咱們的排列組合知識能夠派上用場了)。它須要依賴於上個物品是否放入揹包,因此它是須要依賴於上一個狀態的。
.....
從上面的分析來看,第n個階段揹包的狀態是須要依賴於第n-1個階段的,因此咱們須要把上一個階段的狀態保存下來,才能快速的求出這個階段的狀態,所以狀態轉移矩陣就出來了。咱們這裏須要定義一個二維的數組,來記錄不一樣階段的狀態。以下圖所示:
下面咱們來看代碼是如何實現的:
def bag(weight,n,w): status=[[0 for _ in range(w+1)] for _ in range(n)] status[0][0]=1 if(weight[0]<=w): status[0][weight[0]]=1 for i in range(n): #動態規劃狀態轉移 #不把第i個物品放入揹包 for j in range(w+1): if status[i-1][j] == 1: status[i][j] = 1 #把第i個物品放入揹包 for j in range(w+1-weight[i]): if status[i-1][j] == 1: status[i][j+weight[i]] = 1 #輸出結果 print(status) for i in range(w,-1,-1): if status[n-1][i]==1: return i return 0 weight=[2,2,8,3,5,3] n=6 w=10 print(bag(weight,n,w))
咱們經過把問題分解爲多個階段,每一個階段對應一個決策。而後記錄下每個階段可達的狀態集合(去掉重複的),而後經過當前階段的狀態集合,來推導下一個階段的狀態集合,依次前進,從而把問題解決。
接下來,咱們再來把0,1揹包問題升級一下,引入物品價值這一說。也就是針對一組不一樣價值、不一樣重量的物品,咱們將物品放入揹包中,在知足揹包最大重量的限制條件下,揹包中可裝入物品的總價值最大是多少呢?這個思路和上一個思路相似,我這裏就不在贅述。建議你們先用回溯算法實現,而後畫出遞歸樹,最後寫出狀態轉移矩陣,再實現代碼。我這裏直接給出代碼。若是有問題,歡迎你們留言。
def bag(weight,value,n,w): status=[[-1 for _ in range(w+1)] for _ in range(n)] status[0][0]=0 if(weight[0]<=w): status[0][weight[0]]=value[0] for i in range(n): #動態規劃狀態轉移 #不把第i個物品放入揹包 for j in range(w+1): if status[i-1][j] >= 0: status[i][j] = status[i-1][j] #把第i個物品放入揹包 for j in range(w+1-weight[i]): if status[i-1][j] >= 0: v=status[i-1][j]+value[i] if(v>status[i][j+weight[i]]): status[i][j+weight[i]]=v #輸出結果 print(status) maxV=0 for i in range(w+1): if status[n-1][w]>maxV: maxV=status[n-1][w] return maxV weight=[2,2,8,3,5,3] value=[3,4,12,6,3,2] n=6 w=10 print(bag(weight,value,n,w))
通過這篇文章和上一篇文章,咱們應該對動態規劃有了一個清晰的認識,我會在下一篇把問題抽象一下,看哪類問題適合動態規劃來解決,以及解決動態規劃問題的思考過程是怎麼樣的?爲了避免錯過,請關注公衆號。