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-w_i] + Pi = 9 + 6 = 15\) 大於\(f[i-1, j] = 9\),所以物品a應該放入承重為8的背包。
\(f[i-1, j-w_i] + v_i\) 是選擇\(v_i\)這件物品時,能夠產生的最大價值,而f[i-1, j-Wi] 表示上一個最優狀態,\(j-w_i\)是上一個最優狀態時的背包容量。
抽象
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個物品的重量 ;
}
}
}
參考
表格:http://blog.csdn.net/mu399/article/details/7722810
http://blog.csdn.net/crayondeng/article/details/15784093