揹包問題詳解

01揹包

  一個揹包中容量爲V,如今有N個物品,每一個物品的第i個 物品體積爲weight[i],價值爲value[i],如今往揹包裏面裝東西,怎麼裝能使揹包的內物品價值最大?這是01揹包的最基礎最根本的問題。01表明的意思是該物品取或者不取。順便提一下各類揹包之間的區別,徹底揹包每種物品的數目是無限種,多重揹包的每種物品數目是有限中。html

  用子問題定義狀態:即f[i][v]表示前i件物品恰放入一個容量爲v的揹包能夠得到的最大價值。則其狀態轉移方程即是: ios

        f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]+w[i]}數組

  把這個過程理解爲當取第i件物品是,價值爲f[i-1][v-c[i]]+w[i];測試

  當不取第i件物品時,價值爲f[i-1][v];優化

     做圖即是以下:內層循環爲v從1到V,外層循環是n從1到N;spa

              

  測試代碼:.net

 1 #include<iostream>
 2 using namespace std;
 3 #define  V 1500
 4 unsigned int f[10][V];//全局變量,自動初始化爲0
 5 unsigned int weight[10];
 6 unsigned int value[10];
 7 #define  max(x,y)    (x)>(y)?(x):(y)
 8 int main()
 9 {
10     
11     int N,M;
12     cin>>N;//物品個數
13     cin>>M;//揹包容量
14     for (int i=1;i<=N; i++)
15     {
16         cin>>weight[i]>>value[i];
17     }
18     for (int i=1; i<=N; i++)
19         for (int j=1; j<=M; j++)
20         {
21             if (weight[i]<=j)
22             {
23                 f[i][j]=max(f[i-1][j],f[i-1][j-weight[i]]+value[i]);
24             }
25             else
26                 f[i][j]=f[i-1][j];
27         }
28     
29     cout<<f[N][M]<<endl;//輸出最優解
30 
31 }
 

  01揹包內存優化:可將狀態轉移方程中二維數組轉化爲一維數組,狀態方程爲:unix

                              f[j]=max{f[j],f[j-v[i]]+val[i]}code

  此時須要保證f[j-v[i]]是上一層狀態的,即假如左邊的f[j]是前i件物品,那麼須要保證f[j-v[i]]是前i-1件物品。那麼內層循環必須是逆序,舉個例子,加入是順序遍歷的話:htm

  

物品號         重量(c)          價值(w)
i=1             4                 5

 

i=2             7                 9

 

i=3             5                 6

 

f[v]=max{ f[v],f[v-c[i]]+w[i] }

 

若是v是順序遞增 i=1時,v=4~10 (由於v要至少大於等於c[i]嘛 否則減出個負數沒意義)
                                                                    原先的:  f[0]=0 f[1]=0 f[2]=0 f[3]=0 f[4]=0 f[5]=0 f[6]=0 f[7]=0 f[8]=0 f[9]=0  f[10]=0
---------------------------  i=1  --------------------------------  後來的: f[0]=0 f[1]=0 f[2]=0 f[3]=0 f[4]=5 f[5]=5 f[6]=5 f[7]=5 f[8]=0 f[9]=0  f[10]=0
v=4:
f[4]=max{f[4],f[0]+5}    max{0,5}=5   f[4]=5

 

v=5:
f[5]=max{f[5],f[1]+5}    max{0,5}=5   f[5]=5

 

v=6:
f[6]=max{f[6],f[2]+5}    max{0,5}=5   f[6]=5

 

v=7:
f[7]=max{f[7],f[3]+5}    max{0,5}=5   f[7]=5

 

v=8:
f[8]=max{f[8],f[4]+5}    max{0,10}=10  f[8]=10  (這裏顯然不對,這時i=1,只能放一件物品,然而沒有一個物品的價值爲10的 )

 

v=9:
f[9]=max{f[9],f[5]+5}    max(0,10}=10  f[9]=10

 

v=10:
f[10]=max{f[10],f[6]+5}  max{0,10}=10  f[10]=10

因此必須是逆序:    

1 for(i=1;i<=n;i++)
2       for(j=v;j>=v[i];j--)
3       {
4             f[j]=max{f[j],f[j-v[i]]+val[i]};
5       }

 注:此處沒有對j<v[i]作分類討論是由於當j<v[i]時默認了f[j]等於上一層的f[j],即保持不變。

 

多重揹包

  已知一個容量爲v的揹包和N件物品,第i件物品最多有num[i]件,沒見物品的重量是weight[i],收益是cost[i];

  物品個數N = 3,揹包容量爲V = 8,則揹包能夠裝下的最大價值爲64.

                          

  基本思路:直接擴展01揹包

  狀態轉移方程:

        f[i][j]=max{f[i][j],f[i-1][j-k*weight[i]+k*cost[i]};其中0<=k&&k<=j/weight[i],這是與01揹包不一樣之處,邊界條件。

注:此處爲f[i][v]而不是f[i-1][v],f[i][v]就至關於第i種物品中(m1,m2,m3,m4...)的上一層,上次沒注意而後就wa了。

  直接抄了網上的代碼,以下:

 1 #include <iostream>
 2 using namespace std;
 3 const int N = 3;//物品個數
 4 const int V = 8;//揹包容量
 5 int Weight[N + 1] = {0,1,2,2};
 6 int Value[N + 1] = {0,6,10,20};
 7 int Num[N + 1] = {0,10,5,2};
 8 int f[N + 1][V + 1] = {0};
 9 /*
10 f[i][v]:表示把前i件物品放入容量爲v的揹包中得到的最大收益。
11 f[i][v] = max(f[i - 1][v],f[i - 1][v - k * Weight[i]] + K * Value[i]);其中1 <= k <= min(Num[i],V/Weight[i])
12 //初始化
13 f[i][0] = 0;
14 f[0][v] = 0;
15 */
16 int MultiKnapsack()
17 {
18     int nCount = 0;
19     //初始化
20     for (int i = 0;i <= N;i++)
21     {
22         f[i][0] = 0;
23     }
24     for (int v = 0;v <= V;v++)
25     {
26         f[0][v] = 0;
27     }
28     //遞推
29     for (int i = 1;i <= N;i++)
30     {
31         for (int v = Weight[i];v <= V;v++)
32         {
33             f[i][v] = 0;
34             nCount = min(Num[i],v/Weight[i]);//是當前揹包容量v,而不是揹包的總容量
35             for (int k = 0;k <= nCount;k++)
36             {
37                 f[i][v] = max(f[i][v],f[i - 1][v - k * Weight[i]] + k * Value[i]);
38             }
39         }
40     }
41     return f[N][V];
42 }
43 int main()
44 {
45     cout<<MultiKnapsack()<<endl;
46     system("pause");
47     return 1;
48 }

 一維多重揹包(選擇01揹包思想),將相同的物品看做不一樣的來處理,而後選擇取或者不取,但複雜度比較高

 1 for(i=1;i<=n;i++)
 2 {
 3     for(j=0;j<=b[i];j++)
 4     {
 5         for(m=k;m>=a[i]*j;m--)
 6         {
 7             if(f[m]<(f[m-j*w[i]]+j*val[i]))
 8                 f[m]=f[m-j*w[i]]+j*val[i];
 9         }
10     }
11 }

多重揹包二進制優化模板:(思考在這裏)

 1 for(i=1;i<=n;i++)
 2 {   
 3     p=0;
 4     g=0;
 5     while(b[i]>g)
 6     {
 7         for(j=k;j>=a[i]*g;j--)
 8         {
 9             f[j]=max(f[j],f[j-w[i]*g]+val[i]*g);
10         }
11         b[i]-=g;
12         g=pow(2,p);
13         p++;
14     }
15     for(j=k;j>=w[i]*b[i];--j)
16     {
17         f[j]=max(f[j],f[j-w[i]*b[i]]+val[i]*b[i]);
18     }
19 }

 

徹底揹包

  徹底揹包只是每種物品的數量被放到了無限大,此時相對與多重揹包變化的只是範圍。

    有一個容量爲V的揹包和N件物品,第i件物品的重量是weight[i],收益是cost[i]。每種物品都有無限件,能放多少就放多少。在不超過揹包容量的狀況下,最多能得到多少價值或收益?

   1.基本思路:直接擴展01揹包

  狀態轉移方程:

        f[i][v] = max(f[i ][v],f[i][v - K * weight[i]] + K * Value[i]); 其中 0<= K * weight[i] <= j,(v指此時揹包容量,不是總容量)

 

 1 #include <iostream>
 2 #include <assert.h>
 3 using namespace std;
 4 /*
 5 f[i][v]:前i件物品放入揹包容量爲v的揹包得到的最大收益
 6 
 7 f[i][v] = max(f[i - 1][v],f[i - 1][v - k * Wi] + k * Vi,其中 1<=k<= v/Wi)
 8 
 9 邊界條件
10 f[0][v] = 0;
11 f[i][0] = 0;
12 */
13 
14 const int N = 3;
15 const int V = 5;
16 int weight[N + 1] = {0,3,2,2};
17 int Value[N + 1] = {0,5,10,20};
18 
19 int f[N + 1][V + 1] = {0};
20 
21 int Completeknapsack()
22 {
23     //邊界條件
24     for (int i = 0;i <= N;i++)
25     {
26         f[i][0] = 0;
27     }
28     for (int v = 0;v <= V;v++)
29     {
30         f[0][v] = 0;
31     }
32     //遞推
33     for (int i = 1;i <= N;i++)
34     {
35         for (int v = 1;v <= V;v++)
36         {
37             f[i][v] = 0;
38             int nCount = v / weight[i];
39             for (int k = 0;k <= nCount;k++)
40             {
41                 f[i][v] = max(f[i][v],f[i - 1][v - k * weight[i]] + k * Value[i]);
42             }
43         }
44     }
45     return f[N][V];
46 }
47 
48 int main()
49 {
50     cout<<Completeknapsack()<<endl;     return 1;
53 }

  內存壓縮:能夠接着01揹包內存壓縮的爲何內層循環方向分析,一維01揹包內層循環反向max中的f[j],f[j-w[i]]+val[i]是上一層的,而在徹底揹包中內層循環是從w[i]到v正向,刷新的是當前層的狀態,看不懂能夠再次分析上面的例子

1 for(i=1;i<=n;i++)
2 {
3        for(j=w[i];j<=v;j++)
4        {
5                f[j]=max(f[j],f[j-w[i]]+val[i]);
6        }
7 }

 

 

 2.直接利用多重揹包

  徹底揹包的物品能夠取無限件,根據揹包的總容量V和第i件物品的總重量Weight[i],可知,揹包中最多裝入V/Weight[i](向下取整)件該物品。所以能夠直接改變第i件物品的總個數,使之達到V/Weight[i](向下取整)件,以後直接利用01揹包的思路進行操做便可。

  舉例:物品個數N = 3,揹包容量爲V = 5。

  拆分以前的物品序列:

                                       

  拆分以後的物品序列:

                 

根據上述思想:在揹包的最大容量(5)中,最多能夠裝入1件物品一,所以不用擴展物品一。最多能夠裝入2件物品二,所以能夠擴展一件物品二。同理,能夠擴展一件物品三。

 

 

 

 

 

揹包問題九講:http://love-oriented.com/pack/Index.html

揹包之01揹包、徹底揹包、多重揹包詳解 :http://www.wutianqi.com/?p=539

揹包問題九講筆記_01揹包:http://blog.csdn.net/insistgogo/article/details/8579597

揹包問題九講筆記_徹底揹包:http://blog.csdn.net/insistgogo/article/details/11081025

揹包問題九講筆記_多重揹包:http://blog.csdn.net/insistgogo/article/details/11176693

01揹包、徹底揹包、多重揹包:http://blog.csdn.net/wzy_1988/article/details/12260343

相關文章
相關標籤/搜索