最近開始學習背包問題,在此做些筆記,先學習最簡單的0-1背包問題。
0-1背包問題介紹:
一,背包問題基本解決
有一個背包可以存放M斤物品,有N件物品(每件物品只有1件),他們重量分別是w1,w2,w3..........,他們價值分別是p1,p2,p3...............。問怎么裝載物品,使背包裝的載物品價值最大?
舉例說明:
背包裝10斤物品,有3件物品,重量分別是3斤,4斤,5斤,價值分別是4,5,6;
可以畫一個矩陣,行號0,1,2,3,行號0表示0件物品可放入背包情況下,背包裝載最大價值,行號1表示只有第一件物品可以放入背包情況下,背包裝載最大價值,行號2表示只有第1件和第2件可以放入背包情況下,背包裝載最大價值,......
列號0,1,2,3.............10表示背包剩余可用容量,列號0表示背包已滿時候,剩余容量為0時候,還可以裝載的最大價值;列號1表示背包剩余容量為1時候,還可以裝載的最大價值,......
當行號是3,列號是10時候,價值最大,即c[3][10]值最大.
行號是0,表示有0件物品可放入背包,此時背包裝載0件物品,因此價值都為0,即c[0][0]=c[0][1]=.....c[0][10]=0;
行號是1,表示第一件物品可以放入背包,第一件物品重量是3,當列號為0,1,2時候,一件物品也放不下,背包裝載價值是0,當列號3,4,5...........10可以放下,但是只有一件物品,因此最多獲取價值是4,即c[1][3]=c[1][4]..........c[1][10] = 4;
行號是2,表示第一件和第二件物品可以放入背包,第一件物品重3斤,第二件重4斤,當列號0,1,2時候,一件物品也放不下,背包裝載價值是0,當列號為3時候,可以放下第一件,但是放不下第二件,價值是4,列號為4時候,可以放下第一件也可以放下第二件,此時分為兩種情況,放第二件或不放第二件,取兩者最大值即可。放第二件,剩余容量是0,此時價值是p[2]+容量0,行號是1時最大值,即p[2]+c[1][0]=5,不放第二件,則是剩余容量是4,行號是1時,最大值,即c[1][4] = 4,則5>4,我們存放第二件,此時背包裝載最大價值是5,當列號為5,6時候,價值5,當列號為7時候,max{不放第二件,放第二件},不放第二件,即列號為7,行號為1最大值,c[1][7]=4;放第二件,為:p[2]+列號為7-w[2]=7-4=3,行號為1時最大值,為:p[2]+c[1][3]=5+4=9,9>4,我們選擇存放第二件,此時裝載了兩件物品,列號8,9,10也是9.
當行號是2時,我們可以得出規律:最大值=max{存放第二件,不存放第二件}=max{p[2]+c[1][列號-w[2]],c[1][列號]},而列號就是從1到背包容量V之間某個值。
當行號是1時,我們可以得出規律:最大值=max{存放第一件,不存放第一件}=max{p[1]+c[0][列號-w[1]],c[0][列號]}=max{p[1],0},當列號>=w[1],最大值p[1],否則0。
我們由此得出一個疑問:為什么列號是0,1,2....10,可以這樣(列號=10不變)嗎?因為我們只求背包剩余容量為10時,裝載價值最大值。
答案是不可以的,比如求當行號是3,列號是10最大值,即最大值=c[3][10] = max{p[3]+c[2][5],c[2][10]},欲求c[3][10]必須知道c[2][5]和c[2][10],而c[2][5] = max{p[2] + c[1][1],c[1][5]},欲求c[2][5]必須求c[1][1]和c[1][5],我們求c[i][10]時候會用到c[i][j],此時0<=j<=10,因此我們必須從行號是0開始,把列號0......10之間,所有c[i][j]求出來,因為后面要用到,這是一個不斷遞推過程。
因此,得出結論:
將前i件物品放入背包中,求背包裝載最大價值問題。只考慮第i件物品策略(第i件放還是不放),如果不放第i件物品,求前 i-1 件物品放入容量背包,背包裝載最大價值問題,最大價值為c[i-1][v];如果放第i件物品,求 i-1 件物品放入容量 v-w[i] 容量背包,背包裝載最大價值問題,最大價值為p[i] +c[i-1][v-w[i-1]];
因此c[i][j] = max{c[i-1][v],p[i]+c[i-1][v-w[i-1]]};
因此我們得出核心代碼:
for (int i = 1; i <= N; i++){ for(int j = 1; j <= M; j++){ if (c[i][j] >= w[i]){ if (c[i-1][j] > p[i]+c[i-1][j-w[i]]){ c[i][j] = c[i-1][j]; }else{ c[i][j] = p[i] + c[i-1][j-w[i]]; } }else{ c[i][j] = c[i-1][j]; } } }
下面是用C++簡單實現程序(VS2008通過):

1 #include <cstdio>
2 const int K = 100; 3
4
5 /*
6 輸入格式: 7 10 3 //M=10,N=3 8 3 4 //w[1]=3,p[1]=4 9 4 5 //w[2]=4,p[2]=5 10 5 6 //w[3]=5,p[3]=6 11 */
12
13 int max(int a,int b){ 14 return a>b?a:b; 15 } 16 int main(){ 17 int M,N,w[K],p[K],i,j,c[K][K]; 18 scanf("%d%d",&M,&N); 19 for (i = 1;i <= N;i++) 20 { 21 scanf("%d%d",&w[i],&p[i]); 22 } 23 for (i = 0;i <= N;i++) 24 { 25 for (j = 0;j <= M;j++) 26 { 27 c[i][j] = 0; 28 } 29 } 30
31 for (i = 1;i <= N;i++) 32 { 33 for (j = 1;j <= M;j++) 34 { 35 if (w[i] <= j) 36 { 37 c[i][j] = max(c[i-1][j],c[i-1][j-w[i]]+p[i]); //方式1
38 /* if (c[i-1][j] > c[i-1][j-w[i]]+p[i]) //方式2 39 { 40 c[i][j] = c[i-1][j]; 41 }else{ 42 c[i][j] = c[i-1][j-w[i]]+p[i]; 43 } 44 */
45 }else{ 46 c[i][j] = c[i-1][j]; 47 } 48 } 49 } 50
51 printf("%d\n",c[N][M]); 52 return 0; 53 }
運行結果:
二,背包問題優化解決
在以上解決方案中,所用時間復雜度是O(M*N),空間復雜度是O(M*N),用到了一個M*N的數組。如果仔細觀察之后,發現可以用一維數組代替二維數組。
當行號為0時,數組c[0][0]=c[0][1]=...................=c[0][10] = 0;
當行號為1時,根據c[i][j] = max{c[i-1][j],p[i]+c[i-1][j-w[i]]},得c[1][j]=max{c[0][j],p[1]+c[0][j- w[1]]}。即c[1][0,1,2.......,9,10]由c[0][0,1,2.......,9,10]得到。
因此我們可以利用a[j]表示c[0][j],用b[j]表示c[1][j],首先b[0]=a[0] = 0,b[1]=a[1]=0,.........b[10]=a[10]=0。將數組b和數組a值設為一樣。然后如果w[i]< j,再根據公式b[j]=max{a[j],p[1]+a[j-w[1]]},重新確定b[j]的值,如果w[i] >= j,則不變。
但是我們仔細觀察一下次公式b[j]=max{a[j],p[1]+a[j-w[1]]},發現b[j]得值由a[k],p[1]和w[1]所得,而k的 取值范圍是0<=k<=j,即b[j]由數組a中下標小於j的元素得到。因此我們可以將j從大到小使用來得到b[j],當i = 0時候,a[0] = a[1] = a[2]=a[3]=..............=a[10]=0,當i=1時,先設定 b[0] = a[0],b[1]=a[1],b[2]=a[2],b[3]=a[3],..............=b[10]=a[10],我們先計算 b[10],b[10]=max{a[10],p[1]+a[10-w[1]]}=max{a[10],4+a[7]},此處 p[1]=4,w[1]=3;因為a[10]=b[10],a[7]=b[7],因此b[10]=max{b[10],4+b[7]}=4,至此數組b和 數組a只有第10個元素值不想等,其他元素都相等;b[9]=max{a[9],p[1]+a[9- w[1]]}=max{a[9],4+a[6]},a[9]=b[9],a[6]=b[6],因此b[9]=max{b[9],b[6]+4}=4,至此 數組b和數組a只有第9,10個元素值不想等,其他元素都相等;因此求的 b[8]=max{b[8],4+b[5]},b[7]=max{b[7],4+b[4]}.............b[3]=max{b[3],4+b[0]}。 在求解過程中,我們發現只要按數組b元素下標從大到小求解,就可以用自己求的自己的值。
至此我們可以利用一個一維數組代替二維數組。
核心代碼:
for (int i = 1; i <= N; i++){ for(int j = M; j > 0; j--){ if (c[i] <= j){ if (c[j] > p[i]+c[j-w[i]]){ c[j] = c[j]; }else{ c[j] = p[i] + c[j-w[i]]; } } } }
下面是用C++簡單實現程序(VS2008通過):

1 #include <cstdio>
2 const int K = 100; 3
4
5 /*
6 輸入格式: 7 10 3 //M=10,N=3 8 3 4 //w[1]=3,p[1]=4 9 4 5 //w[2]=4,p[2]=5 10 5 6 //w[3]=5,p[3]=6 11 */
12
13 int max(int a,int b){ 14 return a>b?a:b; 15 } 16
17 int main(){ 18 int M,N,w[K],p[K],i,j,f[K] = {0}; 19 scanf("%d%d",&M,&N); 20 for (i = 1;i <= N;i++) 21 { 22 scanf("%d%d",&w[i],&p[i]); 23 } 24
25 for (i = 1;i <= N;i++) 26 { 27 for (j = M;j > 0;j--) 28 { 29 if (w[i] <= j) 30 { 31 f[j] = max(f[j],p[i]+f[j-w[i]]); 32 } 33 } 34 } 35
36 printf("%d\n",f[M]); 37 return 0; 38 }
運行結果: