0-1揹包 和 部分揹包
關於揹包問題,其實可以分爲兩種類型:0-1揹包問題(動態規劃) 和 部分揹包問題(貪心算法)。
- 0-1揹包問題:每件物品或被帶走,或被留下,(需要做出0-1選擇)。小偷不能只帶走某個物品的一部分或帶走兩次以上同一個物品。
在選擇是否要把一個物品加到揹包中,必須把該物品加進去的子問題的解與不取該物品的子問題的解進行比較。這種方式形成的問題導致了許多重疊子問題,滿足動態規劃的特徵。
- 部分揹包問題:小偷可以只帶走某個物品的一部分,不必做出0-1選擇。
總是選擇每一磅價值 (Vi / Wi) 最大的物品添加進揹包中。那麼其解決過程是:對每磅價值進行排序,依次從大到小選擇添加進揹包中。
更通俗點理解,0-1揹包問題的一件物品可以想象成是一個金錠;而部分揹包問題中的一件物品可以想象成是金粉。
題目描述
假設山洞裏共有a, b, c, d, e這5件寶物(不是5種寶物),它們的重量分別是2,2,6,5,4,它們的價值分別是6,3,5,4,6,現在給你個承重爲10的揹包, 怎麼裝揹包,可以才能帶走最多的財富。
有編號分別爲a,b,c,d,e的五件物品,它們的重量分別是2,2,6,5,4,它們的價值分別是6,3,5,4,6,現在給你個承重爲10的揹包,如何讓揹包裏裝入的物品具有最大的價值總和?
name | weight | value | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
a | 2 | 6 | 0 | 6 | 6 | 9 | 9 | 12 | 12 | 15 | 15 | 15 |
b | 2 | 3 | 0 | 3 | 3 | 6 | 6 | 9 | 9 | 9 | 10 | 11 |
c | 6 | 5 | 0 | 0 | 0 | 6 | 6 | 6 | 6 | 6 | 10 | 11 |
d | 5 | 4 | 0 | 0 | 0 | 6 | 6 | 6 | 6 | 6 | 10 | 10 |
e | 4 | 6 | 0 | 0 | 0 | 6 | 6 | 6 | 6 | 6 | 6 | 6 |
只要你能通過找規律手工填寫出上面這張表就算理解了01揹包的動態規劃算法。
首先要明確這張表是至底向上,從左到右生成的。
講解1:
爲了敘述方便,用e2單元格表示e行2列的單元格,這個單元格的意義是用來表示只有物品e時,有個承重爲2的揹包,那麼這個揹包的最大價值是0,因爲e物品的重量是4,揹包裝不了。
對於d2單元格,表示只有物品e,d時,承重爲2的揹包,所能裝入的最大價值,仍然是0,因爲物品e,d都不是這個揹包能裝的。
同理,c2=0,b2=3,a2=6。
講解2:
對於承重爲8的揹包,a8=15,是怎麼得出的呢?
根據01揹包的狀態轉換方程,需要考察兩個值,一個是f[i-1,j],對於這個例子來說就是b8的值9,另一個是f[i-1, j-Wi]+Pi;
在這裏,f[i-1, j]表示我有一個承重爲8的揹包,當只有物品b,c,d,e四件可選時,這個揹包能裝入的最大價值。
f[i-1, j-Wi]表示我有一個承重爲6的揹包(等於當前揹包承重減去物品a的重量),當只有物品b,c,d,e四件可選時,這個揹包能裝入的最大價值。
f[i-1, j-Wi]就是指單元格b6,值爲9,Pi指的是a物品的價值,即6。
由於f[i−1,j−wi]+Pi=9+6=15f[i−1,j−wi]+Pi=9+6=15 大於f[i−1,j]=9f[i−1,j]=9 ,所以物品a應該放入承重爲8的揹包。
f[i−1,j−wi]+vif[i−1,j−wi]+vi 是選擇vivi 這件物品時,能夠產生的最大價值,而f[i-1, j-Wi] 表示上一個最優狀態,j−wij−wi 是上一個最優狀態時的揹包容量。
抽象
0-1揹包問題子結構:選擇一個給定物品i,則需要 比較(選擇 i 的形成的子問題的最優解) 與 (不選擇 i 的子問題的最優解)。分成兩個子問題,進行選擇比較,選擇最優的。
0-1揹包問題遞歸過程:設有n個物品,揹包的重量爲w,C[i][w]爲最優解。即:
下面給出僞代碼實現:
簡約的實現
通過上面的僞代碼,我們會發現,實現的過程有點羅嗦,下面給出一個簡約點的實現,思路都是一樣的。
僞代碼:
最優值和最優解的求解過程
最優值:求解整個揹包最後的總價值達到最優。求解揹包問題的最優值,關鍵是要弄清楚上面的遞推式子。
最優解:當揹包價值達到最大時,列出所選取的物品都是那些。
代碼的實現過程是這樣的:
int remainspace = W;
//輸出所選擇的物品列表:
for(int i=N; i>=1; i--) {
if (remainspace >= goods[i].weight) {
if ((select[i][remainspace]-select[i-1][remainspace-goods[i].weight]==goods[i].value)) {
cout << "item " << i << " is selected!" << endl;
remainspace = remainspace - goods[i].weight;//如果第i個物品被選擇,那麼揹包剩餘容量將減去第i個物品的重量 ;
}
}
}