0-1背包問題動態規划詳解


最近開始學習背包問題,在此做些筆記,先學習最簡單的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 }
View Code

 運行結果:

二,背包問題優化解決

          在以上解決方案中,所用時間復雜度是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 }
View Code

運行結果:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM