背包问题九讲笔记-01背包问题
問題描述
有N件物品和一個容量為V的背包.放入第i件物品.放入第i件物品耗費的容量是Ci,所獲得的價值是Wi.每件物品只有一個.求將哪些物品放入背包可使價值總和最大.
思考過程
一般來說求極值的問題可分為貪心,動態規劃,以及遍歷所有可能.在這三中方法中,動態規劃是最常見的,也是很難想出來的.其中最難的是定義子問題,寫出動態轉移方程.類似于將問題簡化,如何由較小的子問題,推到出較復雜的問題.類似的計算機的本質是極其簡單的二進制運算,但由無數二進制運算疊加在一起,組成了今天復雜的互聯網系統.
在上次面試中,由于沒想出思路,很緊張,之后寫最簡單的01背包也沒有寫出來.面試后,痛定思痛,之后要逐步加強的自己的算法思維能力,證明問題能力,找錯能力,工具應用能力.
思路
一般可從最簡單入手:
- 1如果沒有物品,解是什么?顯而易見,由于沒有任何物體,得到的價值為0.這一般對應于動態規劃中的初始化.
- 2再復雜一點點,如果有一個物體,如何求解?直接比較物品體積和背包容量,如果小于,直接拿下,解就是物體的價值,否則還是0.
- 3 當有兩個物體時呢?如果先考慮第二個物體,那么有兩種選擇,裝或者不裝,兩種選擇之后,會產生新的容量,以及第一個物體,那么問題回歸到第二種情況.
- 如果有N個物體,如果放完第N個物體(選擇放或者不放)后,那么問題轉化為含有N?1個物體的子問題.這樣問題就逐漸分解了.
定義子問題
用F[i,v]表示前i件物品放入一個容量為v的背包所獲得的最大價值.
狀態轉移方程
在放與不放兩個子問題中選擇一個最大的解
偽代碼
F[0, 0...V] = 0 for i = 1 to N for j = 0 to Vif(C[i] > j)F[i, j] = F[i - 1, j]elseF[i, j] = max(F[i - 1, j], F[i - 1, j - C[i]] + W[i])優化
時間
時間復雜度已無法再優化,.
T=O(NV)空間
這里使用的是二維數據,但求解第i行值時,只利用到i?1行的解,并未利用更久之前的解,由于有這種局限解的特性,可以利用一維的滾動數組模擬二維數據,另外由于這里由i?1的前面的解j?c[i]推導出i后面的解j,也就是利用一維的數據的話,舊解為前面的解,新解為后面的.那么就應該讓j從大到小進行循環遍歷,因為這樣第一次接觸的到為舊解i?1,新出來的新解j在此次遍歷也不會再用到.也就是當且僅使用了一次.如果是從小到大遍歷j,那么新求出的解j+C[i]在后面還會遍歷到,因此還會被重新用到,再次用到的話,還會選擇是否拿下這個物體,不符合題意每件物品只有一個(如果有無窮多個物品,就可以從小到大遍歷,之后的完全背包問題會提到)
S=O(V)優化空間后的偽代碼 1
F[0...V] = 0 for i = 1 to Nfor j = V to C[i]F[j] = max(F[j], F[j - C[i]] + W[i]代碼更將簡單優雅
初始化的細節
如果是求恰好要裝滿背包時的最優解,那么像上面的都初始化為0就不行了.這時候需要只有F[0, 0] = 0, F[0, 1…V] = INT_MIN(表示負無窮).可以理解只有0個物品,背包為0容量正好有解,其余解為無效解.如果手工畫出來子問題轉化圖示,這樣最基礎的子問題只能從F[0,0]出發,不會從其他無效子問題出發,因為初始化無效解是負無窮,兩者去較大的話,肯定不會選取負無窮.
一個常數的優化
可將偽代碼1再次優化為
for i = 1 to Nfor j = V to max(V - sum(C[i...N]), C[i])F[j] = max(F[j], F[j - C[i]] + W[i])由于只需要最后F[V]的值,倒推前一個物品,其實只要知道F[V-C[N]]即可。以此類推,對以第j個背包,其實只需要知道到F[v-sum{C[j…n]}]即可
求取最優方案
參照一般動態規劃問題輸出方案的方法:記錄下每個狀態的最優質是由狀態轉移方程的哪一項得到的,也就是記錄最優解時,是否選擇了這個物品.具體來說是利用path[i, j] 來記錄當前的轉移過程,path[i, j] = true表示選取物品i, path = False 表示未選取當前物品.(bool數組更加節省內存).空間復雜度S=O(VN).
代碼庫(C++)
不輸出最優方案
void zeroOnePack(int c, int w, int V, vector<int> & F){for(int i = V; i >= c; i--)F[i] = max(F[i], F[i - c] + w); } int allZeroOnePack(vector<int>& C, vector<int>& W, int V){vector<int> F(V + 1, 0);for(int i = 0; i < C.size(); i++)zeroOnePack(C[i], W[i], V, F);return F[V]; }輸出最優方案
void zeroOnePack(int i, int c, int w, int V, vector<int> & F, vector<vector<bool> > &path){for(int j = V; j >= c; j--){int choose = F[j - c] + w;if(choose > F[j]){F[j] = choose;path[i][j] = true;}} } int allZeroOnePackDetail(vector<int>& C, vector<int>& W, int V){vector<int> F(V + 1, 0);vector<vector<bool> > path(C.size(), vector<bool>(V + 1, false));for(int i = 0; i < C.size(); i++){zeroOnePack(i, C[i], W[i], V, F, path);}return F[V]; } void printMethod(vector<int>& C){int i = N - 1;int v = V;while(i >= 0){if(path[i][v]){cout<<i<<" ";v = v - C[i];}i--;} }另一個小的常數時間優化
在利用dfs遞歸求解時,先將物品按照單位價格排好序,單位價格高的靠前,這樣如果某個物品超載時,沒必要再累積其后面物品的價格, 而是按照該物品的單位價格乘以剩余容量,這樣算出的總價格雖然比實際裝載的總價格略高些,如果這樣略高于實際值的解還低于當前的最優解,則可對后面剪枝,避免多余的計算.
Reference
總結
以上是生活随笔為你收集整理的背包问题九讲笔记-01背包问题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux系统查看硬盘序列号
- 下一篇: Echarts地图,省市区县,行政代码及