【演算法】01揹包
先看題目
物品不能分隔,必須全部取走或者留下,因此稱為01揹包
(只有不取和取兩種狀態)
看第一個樣例
我們需要把4個物品裝入一個容量為10的揹包
我們可以簡化問題,從小到大入手分析
weight value
2 1
3 3
4 5
7 9
先考慮物品數量為1的情況:
把前1件物品放入容量為1的揹包,此時得到的最大價值是:0
把前1件物品放入容量為2的揹包,此時得到的最大價值是:1
把前1件物品放入容量為3的揹包,此時得到的最大價值是:1
......
把前1件物品放入容量為10的揹包,此時得到的最大價值是:1
把前2件物品放入容量為1的揹包,此時得到的最大價值是:0
把前2件物品放入容量為2的揹包,此時得到的最大價值是:1
把前2件物品放入容量為3的揹包,此時得到的最大價值是:3
把前2件物品放入容量為4的揹包,此時得到的最大價值是:3
把前2件物品放入容量為5的揹包,此時得到的最大價值是:4
把前2件物品放入容量為6的揹包,此時得到的最大價值是:4
......
把前2件物品放入容量為10的揹包,此時得到的最大價值是:4
把前3件物品放入容量為1的揹包,此時得到的最大價值是:0
把前3件物品放入容量為2的揹包,此時得到的最大價值是:1
把前3件物品放入容量為3的揹包,此時得到的最大價值是:3
把前3件物品放入容量為4的揹包,此時得到的最大價值是:5
把前3件物品放入容量為5的揹包,此時得到的最大價值是:5
把前3件物品放入容量為6的揹包,此時得到的最大價值是:6
......
把前3件物品放入容量為10的揹包,此時得到的最大價值是:9
即:先從物品的數量開始列舉,對i件物品列舉所對應的揹包容量j,考慮它的最大價值f[i][j]
for(int i=1;i<=n;i++){//總共有n件物品 for(int j=1;j<=m;j++){//揹包的最大容量是m } }
對於一個f[i][j]:
(1)假設我們不選擇第i件物品,則f[i][j] = f[i-1][j]
,相當於把前i-1件物品放入這個容量為j的揹包,得到的最大值;
(2)假設我們選擇第i件物品,則這個物品剩餘的空間就是j-w[i]
,前一次迴圈的時候必定會算出這個值來,因此可以讓:f[i][j] = f[i-1][j-w[i]] + v[i]
兩種選擇中,我們保留最優解,避免下標出現負數(即j-w[i]
可能為負數)
for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ f[i][j]=f[i-1][j]; if(j>=w[i])f[i][j]=max(f[i][j],f[i-1][j-w[i]]+v[i]); } } printf("%d\n",f[n][m]);
其實這道題還有更多的做法,例如遞迴,從大到小的做法
上面的做法其實是遞推,時間複雜度是O(n*m),相比遞迴好一點,使用這種演算法只要保證n*m在107以內即可。空間複雜度則是O(n*m)
其實上,f陣列是可以壓縮成一維陣列的
對於一個數f[i][j],實際上是隻用到了第i-1行
因此,經過簡單的優化,只需要一行即可,把上一行的資訊移動下來,刪掉所有相關第一維度的東西,看看會變成什麼樣:
f[j]=f[j-w[i]]; //直接存到下一行中
f[j]=f[j];
f[j]=max(f[j],f[j-w[i]]);
對於一維陣列的問題所在:迴圈的順序會發生變化,如果繼續按照從左到右的順序進行迴圈的話,我們會發現,每一個數字都會被後面的資料所引用,因此需要從後往前來遞推。這樣,後面的值更新的時候不會被第二次使用,更新前面的值都不會被影響到。
反正記住是從後往前就行了
完整版的程式碼
for(int i=1;i<=n;i++){
for(int j=m;j>=w[i];j--){
//此處寫j>=w[i]避免下標產生負數
//相當於if(j>=w[i]) f[j]=max(f[j],f[j-w[i]]+v[i]);
f[j]=max(f[j],f[j-w[i]]+v[i]);
}
}