c语言dp算法解决背包问题,DP求解完全背包问题及其优化原理
1.?完全背包問題的形式化描述
完全背包問題是一類經典的DP(Dynamic Programming,動態規劃)問題,問題描述如下:
有n種重量和價值分別為wi,vi的物品,從這些物品中挑選出總重量不超過W的物品,求所有挑選方案中價值vi總和的最大值。值得注意的是,每種物品都可以選取無限次。
假設第i種物品選擇了ki個,ki∈N,則
目標函數為:
max{Σkivi}
約束條件為:
$?\sum k_i w_i \leq W $
2. 完全背包問題的平凡解法
首先對所有物品種類進行1~n的編號。
設函數F(i,j),其意義為:從編號1~i的種類中選取物品,裝入最大允許重量為j的背包中,所能獲得的最大總價值是F(i,j)。
首先考慮基準情況:選0個物品,即i=0,這時:
F(i,j) = F(0,j) = 0
對于子問題F(i,j)的求解,其最優解的決策依賴于涉及1~(i-1)類物品的子問題F(i-1,j)。
當前要做出決策,需選k個i類物品(k可以為0),根據物品的重量分為如下兩種情況:
1。若物品重量wi不超過背包的允許重量j,則可分別選0個、...、k個該物品,kwi都不超過允許重量j。最后再從所有選擇結果中挑選最大總價值,作為當前最優解。
F(i,j) = max{ F(i-1,j - k ×?wi) + k ×?vi|?(k>=0,kwi<=j)}
2。若物品重量wi超過背包的允許重量j,則無法放入當前物品:
F(i,j) = F(i-1,j)
上述兩個遞推式揭示了當前狀態與上一狀態之間的轉移關系。加上基準情況,我們發現算法已經封閉了,可以編程實現。
核心程序
輸入:物品種類數n,背包最大允許重量t,第i種物品重量w[i]、價值v[i]。
輸出:最大價值dp[n][t]。
初始化:清零dp數組
for(int i=1;i<=n;++i) {for(int j=0;j<=t;++j) {if(j
dp[i][j]=dp[i-1][j];
}else{for(int k=0;k*w[i]<=j;++k) {
dp[i][j]=max(dp[i][j], max(dp[i-1][j], dp[i-1][j-k*w[i]]+k*v[i]));
}
}
}
}
可以看出漸近復雜度O(n×t×w),為三次復雜度,因此該程序能解決的問題規模非常有限。但正如標題所說的,平凡解法還有較大可優化的空間。
3. 利用數據相關性——時間復雜度優化
利用循環間數據相關性優化算法是一種常見的技巧。例如“最大子段和”問題的蠻力解法中,利用數據相關性可以將O(n^3)的復雜度直接優化為O(n^2)。同理,在求Σnx=1?x!(連續階乘和)問題中,利用數據相關性可將O(n^2)直接優化為O(n)。這些例子展現了該技巧的生命力。
回到我們的DP解法,觀察for(j)與for(k)循環,猜測兩個循環間存在數據相關性,例如:F(i-1,j)中選擇k個物品的情況,與F(i-1,j-wi)中選擇k-1個情況完全相同,推測F(i-1,j)的遞推中k>=1的部分在計算F(i-1,j-wi)時就已經求出了,這意味著k循環除了第一趟執行對結果有貢獻外,后續執行只是在重復之前已經完成的計算,有希望將k循環合并到j循環中。
為了驗證這種想法,推導如下:
題設:
F(i,j) = max{F(i-1,j - k?×?wi) + k?×?vi|(k>=0)}? ……①
考慮當k==0時,F(i-1,j - k?×?wi) + k?×?vi退化為F(i-1,j),所以
F(i,j) =?max(F(i-1,j), max{F(i-1,j - k?×?wi) + k?×?vi|(k>=1)} )
為了驗證之前的猜測,將k代換為k+1。問題等價變形到k>=0的情況:
F(i,j) =?max(F(i-1,j), max{F(i-1,j - (k+1) ×?wi) + (k+1) ×?vi|(k>=0)})
=?max(F(i-1,j), max{F(i-1,(j -?wi)-?k ×?wi) + k ×?vi+?vi|(k>=0)} )? ??……②
結論仍不明顯,將上式②下劃線部分提到max{}后面,發現:
F(i,j) =?max(F(i-1,j), max{F(i-1,(j -?wi)-?k ×?wi) + k ×?vi|(k>=0)}?+vi)
對比①知,上式藍色部分恰等價于F(i, j -?wi),這基本驗證了我們的想法。
F(i,j) =?max(F(i-1,j),?F(i, j -?wi)?+?vi)
這便是最終的表達式,可以看到成功消去了k。
根據推導的狀態轉移方程編寫程序,時間復雜度直接從三次降至了二次。
需要注意,因為F(i-1,j)依賴于F(i-1,j-wi)的計算結果,所以j必須遞增枚舉,才能保證構造的正確性。
for(int i=1;i<=n;++i) {for(int j=0;j<=t;++j) {if(j
dp[i][j]=dp[i-1][j];
}else{
dp[i][j]=max(dp[i-1][j], dp[i][j-w[i]]+v[i]);
}
}
}
4. 降低dp數組維度——空間復雜度優化
優化前,dp數組的空間復雜度是O(n×t)的。
注意到dp數組中,dp[i]行的各元素值只依賴于前一行dp[i-1],而不依賴于dp[i-2]、……、dp[0]行。這啟發我們只存儲一行dp數組,然后在該行“原地”更新數據。
仍然有兩種情況:
1。對于j
2。對于其它非1。的情況,需要更新dp行,dp[j]=max(dp[j], dp[j-w[i]]+v[i]); 加粗部分反映了“原地”更新策略。
for(int i=1;i<=n;++i) {for(int j=w[i];j<=t;++j) {
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
}
}
因為情況1。的存在,j的取值必須以w[i]為區間起點。
值得注意的是,j循環按遞增順序枚舉,這與我們在第3節中得出的結論一致。
經過優化,空間復雜度降至了O(n))。
5. 結論
首先描述了完全背包問題,然后分析了最直觀的trivial解法,由平凡解法發現循環間的數據相關性,推導出優化后的狀態轉移方程,推導結論使算法時間復雜度降至O(n×t)。接下來從另一角度——空間復雜度分析dp數組降維優化,使空間復雜度降低至O(n)。綜合時空兩個方面,得出了DP求解此類問題的優化方法。
總結
以上是生活随笔為你收集整理的c语言dp算法解决背包问题,DP求解完全背包问题及其优化原理的全部內容,希望文章能夠幫你解決所遇到的問題。