本實驗要求基於算法設計與分析的通常過程(即待求解問題的描述、算法設計、算法描述、算法正確性證實、算法分析、算法實現與測試),在針對0-1揹包問題求解的實踐中理解動態規劃 (Dynamic Programming, DP) 方法的思想、求解策略及步驟。算法
做爲挑戰:能夠考慮基於跳躍點的改進算法,以及對連續型物品重量/揹包容量的支持。數組
理解問題,給出問題的描述。數據結構
n個物體,1個揹包。對物品i,其價值爲\(v_i\),重量爲\(W_i\),揹包的容量爲 \(W\),如何選取物品,是的揹包中裝入的物品的總價值最大?函數
在約束條件爲:選取物品的重量小於等於揹包重量的狀況下,儘量讓揹包中物品的總價值最大。測試
根據問題描述,設計以下的約束條件和目標函數:優化
約束條件:spa
\[ \begin{equation} \left\{ \begin{array}{**lr**} \sum_{i=1}^{n} w_ix_i \leq W \\ x_i \in \{0,1\}, 1 \leq i \leq n & \end{array} \right. \end{equation} \]設計
目標函數:code
\[ max\sum_{i=1}^{n} v_ix_i \]
問題如今等價於,尋找一個在知足約束條件狀況下,並使目標函數達到最大的解 \(X=(x_1,x_2,...,x_n)\)
算法設計,包括策略與數據結構的選擇
設計用二維數組對物品信息進行記錄: \(C[i][j]\) 用來記錄若是當前還有\(i\)個物品,揹包容量還剩\(j\)的狀況下,當前揹包所能獲得的最大價值。
很容易發現條件即:\[C[0][j] = C[i][0] = 0\]
遞歸定義應該爲:
\[ \\ C[i][j]= \begin{equation} \left\{ \begin{array}{**lr**} C[i-1][j], & j < w_i \\ max\{C[i-1][j],C[i-1][j-w_i]+v_i\}, & j \ge w_i \end{array} \right. \end{equation} \]
能夠這樣理解,每一個物品我能夠選擇是否加入到揹包中,首先判斷,當前物品是否重量已經大於揹包所能容納的重量;若是能容納該物體,則進行判斷加入該物品\(C[i-1][j-w_i]+v_i\)獲得的價值更高,仍是不加入該物品\(C[i-i][j]\)所能獲得的物品的總價值更高。
描述算法。但願採用源代碼之外的形式,如僞代碼或流程圖等;
僞代碼表示:
01PACKAGE(n,w,v,W) // n爲物品的個數,w爲重量, v爲價值,W爲揹包容量 // C[1..n,1..n]爲最優解 for i=1 to n: do C[i][0] = 0 for j=1 to W: do C[0][j] = 0 for i=1 to n: for j=1 to W: do if j < w[i]: then C[i][j]=C[i-1][j] else then C[i][j]=max{C[i-1][j],C[i-1][j-w[i]]+v[i]} return C
算法的正確性證實。須要這個環節,在理解的基礎上對算法的正確性給予證實;
對該算法進行最優子結構的證實:
假設\(X=(x_1,x_2,x_3,...,x_n)\)是揹包問題的最優解,那麼\((x_2,..,x_n)\)是下面問題的一個最優解:
\[ \begin{equation} \left\{ \begin{array}{**lr**} \sum_{i=2}^{n} w_ix_i \leq W-w_1x_1 \\ x_i \in \{0,1\}, 2 \leq i \leq n & \end{array} \right. \end{equation} \]
目標函數: \[max\sum_{i=2}^nv_ix_i\]
即除去第一個物品之後的子問題
證實以下:(反證法)
設\(X=(x_2,...,x_n)\)不是上述子問題的最優解,設\(Y=(y_2,...,y_n)\)是上述問題的最優解,則\(Y\)所求的目標函數的值必定比X求得的目標函數的值更大,即:
\[ \sum_{i=2}^nv_iy_i > \sum_{i=2}^nv_ix_i \]
又\(Y\)知足約束條件:\(\sum_{i=2}^nw_iy_i \leq W-w_1x_1\),即 \(w_1x_1+\sum_{i=2}^nw_iy_i \leq W\),該不等式證實\((x_1,y_1,y_2,...,y_n)\)是原問題的一個解。
在公式 \((5)\) 中左右同時加上\(v_1x_1\),可得:\[ v_1x_1+\sum_{i=2}^nv_iy_i > v_1x_1+\sum_{i=2}^nv_ix_i\],說明\((x_1,y_1,y_2,...,y_n)\)要比\((x_1,x_2,...,x_n)\) 方案價值更高,因此\((x_1,x_2,...,x_n)\)不是最優解,產生了矛盾。
因此其最優子結構的性質得證。
算法複雜性分析,包括時間複雜性和空間複雜性;
時間複雜性分析:
因爲只須要遍歷進行,因此只須要考慮循環中的複雜度便可。
\[ T(n) =O(n)+O(W)+O(nW) \\ =O(nW) \]
空間複雜度,主要是生成數組時佔用的空間。
\[ T(n)=O(n^2) \]
時間複雜度分析:
只須要一個循環便可。
\[ T(n)=O(n) \]
空間複雜度爲:\[O(1)\]
算法實現與測試。附上代碼或以附件的形式提交,同時貼上算法運行結果截圖;
# -*- coding: utf-8 -*- """ Created on Fri Sep 28 12:44:40 2018 @theme: 算法準備-01揹包問題 @author: pprp """ import numpy as np def solvePackage(n,w,v,W): """solve the 01 package problem""" C=np.zeros((n+1,W+1)) for i in range(n): C[i][0]=0 for i in range(W): C[0][i]=0 for i in range(1,n+1): for j in range(1,W+1): if j < w[i-1]: C[i][j]=C[i-1][j] else: C[i][j]=max(C[i-1][j],C[i-1][j-w[i-1]]+v[i-1]) return C def getSolution(n,w,W,C): j = W x = np.zeros(n) for i in range(n,0,-1): if C[i][j]==C[i-1][j]: x[i-1] = 0 else: x[i-1] = 1 j -= w[i-1] return x if __name__ == "__main__": n = 11 w = np.array([2, 6, 3, 4, 2, 8, 2, 4, 7, 5, 1]) v = np.array([10,23,5,34,23,17,22,32,12,15,32]) W = 15 C=solvePackage(n,w,v,W) x=getSolution(n,w,W,C) print("packages:",x) print("Output:\n",C)
packages: [1. 0. 0. 1. 1. 0. 1. 1. 0. 0. 1.]
驗證結果:10+34+23+22+32+32=153
動態規劃算法一般是用來解決某種最優性質的問題。基本思想是將帶求解問題劃分爲若干個子問題,先求解子問題,而後從子問題的解獲得原問題的解。動態規劃與分治法的區別在於,動態規劃的子問題多是互相重疊的重複計算的,分治法則是相互獨立的。能夠用一個表來記錄子問題是否已經求解,這樣能夠避免重複求解。
須要知足最優化原理、無後效性和重疊性。
最優化原理,一個最優化策略的子策略必定是最優的,就是知足最優子結構的性質。
重疊性,就是記錄已經解決過的問題,須要存儲已經解決過的問題,空間複雜度比較大,是一種以空間換時間的算法。
動態規劃的難點在於,如何根據問題的最優子結構的性質,構造動態規劃方法中的遞歸公式或動態規劃方程。就好比本問題中,如何設計這個方程纔是難點所在。
在逆向求解使用的哪幾個揹包的時候,因爲對問題理解的不深入,致使彙總的時候發現計算結果出現了問題,也就是\(getSolution\)這個函數出現了問題。應該逆向進行求解問題,也就是從後往前進行推導,更改了循環的方向之後就能夠獲得最終的結果了。
只有在真正理解算法的基礎上,而後加以僞代碼的梳理,這時候寫才能一鼓作氣。另外還須要對代碼計算出來的結果進行人工覈查,防止某些問題被忽略掉。另外這個問題已經被老師分析的比較透徹,因此寫起來沒有太大的困難。可是碰見新的問題的時候,如何構造解決問題的方法纔是難點所在。