0-1揹包:使用滾動陣列時為何要逆序列舉
問題簡述:有一揹包,最大體積是10,有三個物品,體積分別是3,4,5,重量分別是4,5,6,求在不超過揹包體積的前提下,所放物品的最大重量是多少。
答:最大重量是11,選擇的物品是2和3,其體積是9,小於揹包體積10
我們已經知道,對於0-1揹包問題,我們可以使用動態規劃進行解決。
定義f(i,j):把前i個物品裝入體積為j的揹包中的最大重量
那麼:f(i,j)=max{f(i-1,j),f(i-1,j-v[i]+w[i]}
根據該方程,我們可以用下面的程式解決上述問題:
輸出結果:#include <iostream> #include <string.h> #include <algorithm> #include <stdio.h> using namespace std; int main() { int max_v = 10; // 揹包最大體積 int v[4] = {0,3,4,5}; // 體積 int w[4] = {0,4,5,6}; // 重量 int f[11][11]; memset(f,0,sizeof(f)); // f(i,j):把前i個物品裝到容量是j的揹包中的最大總重量 // f(i,j)=max{f(i-1,j),f(i-1,j-v[i]+w[i]} // 一定要牢記:我們要計算的是:f(3,10) !!! 什麼意思?對我們來講,11*11的陣列,只有f(3,10)對我們來講是有意義的,其它的空間能省則省。^_^ for (int i=1; i<=3; i++) { for (int j=0;j<=10; j++) { f[i][j] = f[i-1][j]; // 如果少了這句,當j<v[i]時,f[i][j]將不被更新,那麼f[2][3]可就是0而不是4了。 if (j >= v[i]) { f[i][j]= max(f[i-1][j],f[i-1][j-v[i]]+w[i]); } } } // 輸出 for (int i=0; i<=3; i++) { for (int j=0; j<=10; j++) { printf("%4d",f[i][j]); } printf("\n"); } printf("The only answer we need is %d\n",f[3][10]); }
對這個答案我們滿意嗎?
如果從最終答案的角度來講,可以接受,畢竟我們得出了想要的解。可是,當資料量很大的時候,比如有m個物品,揹包體積是n,那麼我們需要定義陣列為f[m][n],其空間複雜度為O(m*n)。而誠如我們所說過的那樣,我們僅僅想知道的是揹包能裝下的最大重量(不要求列印方案),即f[n][m]的值,那麼,有必要申請n*m的陣列嗎?或者說,有無方法可以降低空間複雜度呢?答案是有的,即滾動陣列。
觀察上圖,除最後一行外,其餘資料是多餘的.....
由f(i,j)=max{f(i-1,j),f(i-1,j-v[i]+w[i]},我們可以簡化為f(j)=max{f(j),f(j-v[i])+w[i]}
程式如下:
#include <iostream> #include <string.h> #include <algorithm> #include <stdio.h> using namespace std; int main() { int max_v = 10; // 揹包最大體積 int v[4] = {0,3,4,5}; // 體積 int w[4] = {0,4,5,6}; // 重量 int f[11]; memset(f,0,sizeof(f)); // f(j):容量是j的揹包中的最大總重量 // f(j)=max{f(j),f(j-v[i]+w[i]} for (int i=1; i<=3; i++) { for (int j=10;j>=0; j--) // 此處要逆序列舉容量 { if (j >= v[i]) { f[j]= max(f[j],f[j-v[i]]+w[i]); } } // 輸出 for (int j=0; j<=10; j++) { printf("%4d",f[j]); } printf("\n"); } printf("The only answer we need is %d\n",f[10]); }
輸出結果:
為什麼計算時要逆序列舉?計算順序所決定!請看下圖:
如當i=1時,f(10)=max{f(10),f(10-v[1])+w[1]}=max{f(10),f(7)+w[1]}=max{0,4}=4
即f(10)依賴的是f(10)和f(7)的值,需要注意的是,此時的f(10)和f(7)是i=0時的f(10)和f(7),如果使用二維陣列儲存,無需擔心覆蓋問題,對體積的列舉逆序或正序都可,但是如果使用一維陣列,若正序列舉會先計算f(7),那麼,再計算f(10)時,i=0時的f(7)已被覆蓋矣!