【演算法】0/1 背包問題(Knapsack Problem)
簡介
發明 DP 的真是個天才、注意看,這個會 DP 的男人太狠了
0/1 背包問題的題目在於物品的放或不放,並且在有限重量的背包中,獲得最高價值。
通常可以想到用暴力(就是數學 n 個物品取或不取的兩種可能),但是 真的很恐怖,資料一多,馬上 TLE。
因此利用陣列紀錄每個物品在某重量該放與不放,可以紀錄之前的資料又不會重新計算。
原理
假設你的背包可以放 10Kg,物品如下:
Weight | Value | |
---|---|---|
Item A | 5 | 4 |
Item B | 1 | 2 |
Item C | 3 | 3 |
Item D | 4 | 6 |
這些物品在背包內可以利用動態規劃,找出可容納的最大價值。
以二維的 DP 為例,要執行它,我們要先定義幾件事情:
int dp[i][j]
i
表示第 i 件物品j
表示背包為 j 重量時
所以你的程式碼應該像是這樣:
1 | int n = 4; // 4 items |
以上是初始化的步驟,接著,我們就可以來判斷和計算了。
首先我們要知道的點是:i 表示物品、j 表示重量
轉換成陣列即是:
j(weight) | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|---|
i(Item) | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
當我們放入 Item A(W:5 V:4) 時:
j(weight) | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|---|
i(Item) = 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
i(A) = 1 | 0 | 0 | 0 | 0 | 0 | 4 | 4 | 4 | 4 | 4 | 4 |
可以看到當 j = 5
時,我們才放進包包。OK,這裡沒有需要判斷的地方,應該沒什麼問題。
當我們放入 Item B(W:1 V:2) 時:
j(weight) | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|---|
i(Item) = 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
i(A) = 1 | 0 | 0 | 0 | 0 | 0 | 4 | 4 | 4 | 4 | 4 | 4 |
i(B) = 2 | 0 | 2 | 2 | 2 | 2 | 4 | 6 | 6 | 6 | 6 | 6 |
uhh,出現了有趣的地方,尤其是 j = 4~6
的部分。
這裡就出現了需要判斷的地方,我們要判斷的東西就是我們要放還是不放?
我們知道 j = 1
時可以放 Item B,是因為前面的物品沒有放進包包所以不用看出怎麼判斷。
但是當我們遇到 j = 5
時,就要判斷:
(1). 我們是否可放?
(2). 放了是否小於原本的價值?
照這個判斷,我們可以寫出:
若放不了,
1 | if (j >= item_w[i - 1]) { // (1). if bag weight is equal or bigger than item weight |
而 (2). 放了是否大於原本的價值?這個問題就涉及到了上個 Item 在包包的狀態,也就是 i - 1
。
我們可以得出:
1 | if (dp[i - 1][j - item_w[i - 1]] + item_v[i - 1] > dp[i - 1][j]) { |
剩下的就是重複步驟。Item C(W: 3, V: 3)
j(weight) | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|---|
i(Item) = 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
i(A) = 1 | 0 | 0 | 0 | 0 | 0 | 4 | 4 | 4 | 4 | 4 | 4 |
i(B) = 2 | 0 | 2 | 2 | 2 | 2 | 4 | 6 | 6 | 6 | 6 | 6 |
i(C) = 3 | 0 | 2 | 2 | 3 | 5 | 5 | 6 | 6 | 7 | 9 | 9 |
Item D(W: 4, V: 6)
j(weight) | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|---|
i(Item) = 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
i(A) = 1 | 0 | 0 | 0 | 0 | 0 | 4 | 4 | 4 | 4 | 4 | 4 |
i(B) = 2 | 0 | 2 | 2 | 2 | 2 | 4 | 6 | 6 | 6 | 6 | 6 |
i(C) = 3 | 0 | 2 | 2 | 3 | 5 | 5 | 6 | 6 | 7 | 9 | 9 |
i(D) = 4 | 0 | 2 | 2 | 3 | 6 | 8 | 8 | 9 | 11 | 11 | 12 |
此時 dp[n][m]
即為解答,當問題縮小時,亦可直接找到答案:如詢問當背包上限為 8kg 時,答案即為 11
完整程式碼
二維版(上述原理)
1 | int n = 4; // 4 items |
一維版
交互陣列版
問題
- ZeroJudge b184. 5. 裝貨櫃問題