徹底揹包完美演繹

揹包問題——「徹底揹包」詳解及實現(包含揹包具體物品的求解) ios

徹底揹包是在N物品中選取若干件(同一種物品可屢次選取)放在空間爲V的揹包裏,每物品的體積爲C1,C2,…,Cn,與之相對應的價值爲W1,W2,…,Wn.求解怎麼裝物品可以使揹包裏物品總價值最大。 算法

動態規劃(DP): 數組

        1) 子問題定義:F[i][j]表示前i物品中選取若干件物品放入剩餘空間爲j的揹包中所能獲得的最大價值。 測試

        2) 根據第i物品放多少件進行決策 優化

                                     (2-1) spa

        其中F[i-1][j-K*C[i]]+K*W[i]表示前i-1物品中選取若干件物品放入剩餘空間爲j-K*C[i]的揹包中所能獲得的最大價值加上k件第i物品; .net

       設物品種數爲N,揹包容量爲V,第i物品體積爲C[i],第i物品價值爲W[i]。 blog

       與01揹包相同,徹底揹包也須要求出NV個狀態F[i][j]。可是徹底揹包求F[i][j]時須要對k分別取0,…,j/C[i]求最大F[i][j]值,耗時爲j/C[i]。那麼總的時間複雜度爲O(NV∑(j/C[i])) 排序

由此寫出僞代碼以下: 索引

[cpp]   view plain copy
  1. F[0][] ← {0}  
  2.   
  3. F[][0] ← {0}  
  4.   
  5. for i←1 to N  
  6.   
  7.     do for j←1 to V  
  8.   
  9.         do for k←0 to j/C[i]  
  10.   
  11.            if(j >= k*C[i])  
  12.   
  13.                 then F[i][k] ← max(F[i][k],F[i-1][j-k*C[i]]+k*W[i])  
  14.   
  15. return F[N][V]  

以上僞代碼數組均爲基於1索引,即第一件物品索引爲1。空間複雜度O(VN)、時間複雜度爲O(NV∑(j/C[i]))

        簡單優化:

        若兩件物品知足C[i] ≤C[j]&&W[i] ≥W[j]時將第j種物品直接篩選掉。由於第i種物品比第j種物品物美價廉,用i替換j獲得至少不會更差的方案。

       這個篩選過程以下:先找出體積大於揹包的物品直接篩掉一部分(也可能一種都篩不掉)複雜度O(N)。利用計數排序思想對剩下的物品體積進行排序,同時篩選出同體積且價值最大的物品留下,其他的都篩掉(這也可能一件都篩不掉)複雜度O(V)。整個過程時間複雜度爲O(N+V)

 

       轉化爲01揹包:

       由於同種物品能夠屢次選取,那麼第i種物品最多能夠選取V/C[i]件價值不變的物品,而後就轉化爲01揹包問題。整個過程的時間複雜度並未減小。若是把第i種物品拆成體積爲C[i]×2k價值W[i]×2k的物品,其中知足C[i]×2k≤V。那麼在求狀態F[i][j]時複雜度就變爲O(log2(V/C[i]))。整個時間複雜度就變爲O(NVlog2(V/C[i]))

 

時間複雜度優化爲O(NV)

將原始算法的DP思想轉變一下。

設F[i][j]表示出在前i種物品中選取若干件物品放入容量爲j的揹包所得的最大價值。那麼對於第i種物品的出現,咱們對第i種物品放不放入揹包進行決策。若是不放那麼F[i][j]=F[i-1][j];若是肯定放,揹包中應該出現至少一件第i種物品,因此F[i][j]種至少應該出現一件第i種物品,即F[i][j]=F[i][j-C[i]]+W[i]。爲何會是F[i][j-C[i]]+W[i]?由於F[i][j-C[i]]裏面可能有第i種物品,也可能沒有第i種物品。咱們要確保F[i][j]至少有一件第i件物品,因此要預留C[i]的空間來存放一件第i種物品。

狀態方程爲:

                           (2-2)

僞代碼爲:

[cpp]   view plain copy
  1. F[0][] ← {0}  
  2.   
  3. F[][0] ← {0}  
  4.   
  5. for i←1 to N  
  6.   
  7.     do for j←1 to V  
  8.   
  9.         F[i][j] ← F[i-1][j]  
  10.   
  11.         if(j >= C[i])  
  12.   
  13.             then F[i][j] ← max(F[i][j],F[i][j-C[i]]+ W[i])  
  14.   
  15. return F[N][V]  

        具體揹包中放入那些物品的求法和01揹包狀況差很少,從F[N][V]逆着走向F[0][0],設i=N,j=V,若是F[i][j]==F[i][j-C[i]]+W[i]說明包裏面有第i件物品,同時j -= C[i]。徹底揹包問題在處理i自減和01揹包不一樣,01揹包是無論F[i][j]與F[i-1][j-C[i]]+W[i]相不相等i都要減1,由於01揹包的第i件物品要麼放要麼不放,無論放仍是不放其已經遍歷過了,須要繼續往下遍歷而徹底揹包只有當F[i][j]與F[i-1][j]相等時i才自減1。由於F[i][j]=F[i-1][j]說明揹包裏面不會含有i,也就是說對於前i種物品容量爲j的揹包所有都放入前i-1種物品才能實現價值最大化,或者直白的理解爲前i種物品中第i種物品物不美價不廉,直接被篩選掉。

        打印揹包內物品的僞代碼以下:

[cpp]   view plain copy
  1. i←N  
  2.   
  3. j←V  
  4.   
  5. while(i>0 && j>0)  
  6.   
  7.      do if(F[i][j]=F[i][j-C[i]]+W[i])  
  8.   
  9.           then Print W[i]  
  10.   
  11.                j←j-C[i]  
  12.   
  13.         else  
  14.   
  15.           i←i-1  

        和01揹包同樣,也能夠利用一個二維數組Path[][]來標記揹包中的物品。開始時Path[N][V]初始化爲0,當 F[i][j]==F[i][j-C[i]]+W[i]時Path[i][j]置1。最後經過從Path[N+1][V+1]逆着走向Path[0][0]來獲取揹包內物品。其中Path[0][]與Path[][0]爲邊界。一樣,在打印路徑的時候當Path[][]=1時,打印W[i];Path[][]=0時i自減1.

       加入路徑信息的僞代碼以下:

[cpp]   view plain copy
  1. F[0][] ← {0}  
  2.   
  3. F[][0] ← {0}  
  4.   
  5. Path[][] ← 0  
  6.   
  7. for i←1 to N  
  8.   
  9.     do for k←1 to V  
  10.   
  11.         F[i][k] ← F[i-1][k]  
  12.   
  13.         if(k >= C[i] && F[i][k] < F[i][k-C[i]]+W[i])  
  14.   
  15.             then F[i][k] ← F[i][k-C[i]]+W[i]  
  16.   
  17.                  Path[i][k] ← 1  
  18.   
  19. return F[N][V] and Path[][]  

打印揹包內物品的僞代碼以下:

[cpp]   view plain copy
  1. i←N  
  2.   
  3. j←V  
  4.   
  5. while(i>0 && j>0)  
  6.   
  7.      do if(Path[i][j]=1)  
  8.   
  9.           then Print W[i]  
  10.   
  11.                j←j-C[i]  
  12.   
  13.         else  
  14.   
  15.           i←i-1  

優化空間複雜度爲O(V)

        和01揹包問題同樣,徹底揹包也能夠用一維數組來保存數據。算法樣式和01揹包的很類似,惟一不一樣的是對V遍歷時變爲正序,而01揹包爲逆序。01揹包中逆序是由於F[i][]只和F[i-1][]有關,且第i的物品加入不會對F[i-1][]狀態形成影響。而徹底揹包則考慮的是第i物品的出現的問題,第i種物品一旦出現它勢必應該對第i種物品還沒出現的各狀態形成影響。也就是說,原來沒有第i種物品的狀況下可能有一個最優解,如今第i種物品出現了,而它的加入有可能獲得更優解,因此以前的狀態須要進行改變,故須要正序。

狀態方程爲:

                          (2-3)

 

僞代碼以下:

[cpp]   view plain copy
  1. F[] = {0}  
  2.   
  3. for i←1 to N  
  4.   
  5.     do for k←C[i] to V  
  6.   
  7.         F[k] ← max(F[k],F[k-C[i]]+W[i])  
  8.   
  9. return F[V]  

        具體揹包中放入那些物品的求法和上面空間複雜度爲O(NV)算法同樣,用一個Path[][]記錄揹包信息。但這裏面是當F[i]=F[i-C[i]]+W[i]時將Path置1.

        僞代碼以下:

[cpp]   view plain copy
  1. F[0][] = {0}  
  2.   
  3. F[][0] = {0}  
  4.   
  5. Path[][] ← 0  
  6.   
  7. for i←1 to N  
  8.   
  9.     do for k←C[i] to V  
  10.   
  11.         if(F[i] < F[k-C[i]]+W[i])  
  12.   
  13.             then F[i] ← F[k-C[i]]+W[i]  
  14.   
  15.                  Path[i][k] ← 1  
  16.   
  17. return F[N][V] and Path[][]  

        打印路徑的僞代碼和前面未壓縮空間複雜度時的僞代碼同樣,這裏再也不重寫。

 

         舉例:表2-1爲一個揹包問題數據表,設揹包容量爲10根據上述解決方法可獲得對應的F[i][j]如表2-2所示,最大價值即爲F[6][10].

表2-1揹包問題數據表

物品號i 1 2 3 4 5 6
體積C 3 2 5 1 6 4
價值W 6 5 10 2 16 8

 

表2-2前i件物品選若干件放入空間爲j的揹包中獲得的最大價值表

  0 1 2 3 4 5 6 7 8 9 10
0 0 0 0 0 0 0 0 0 0 0 0
1 0 0 0 6 6 6 12 12 12 18 18
2 0 0 5 6 10 11 15 16 20 21 25
3 0 0 5 6 10 11 15 16 20 21 25
4 0 2 5 7 10 12 15 17 20 22 25
5 0 2 5 7 10 12 16 18 21 23 26
6 0 2 5 7 10 12 16 18 21 23 26

 下面針對前面提到的表2-1提供兩種方法的測試代碼:

 

[cpp]   view plain copy
  1. #include <iostream>  
  2. #include <cstring>  
  3. #include "CreateArray.h"        //該頭文件用於二維數組的建立及銷燬,讀者本身實現  
  4.   
  5. using namespace std;  

 

//時間複雜度O(VN),空間複雜度爲O(VN)

[cpp]   view plain copy
  1. int Package02(int Weight[], int Value[], int nLen, int nCapacity)  
  2. {  
  3.     int** Table = NULL;  
  4.     int** Path = NULL;  
  5.     CreateTwoDimArray(Table,nLen+1,nCapacity+1);    //建立二維數組  
  6.     CreateTwoDimArray(Path,nLen+1,nCapacity+1); //建立二維數組  
  7.       
  8.     for(int i = 1; i <= nLen; i++)  
  9.     {  
  10.         for(int j = 1; j <= nCapacity; j++)  
  11.         {  
  12.             Table[i][j] = Table[i-1][j];  
  13.             if(j >= Weight[i-1] && Table[i][j] < Table[i][j-Weight[i-1]]+Value[i-1])  
  14.             {  
  15.                 Table[i][j] = Table[i][j-Weight[i-1]]+Value[i-1];  
  16.                 Path[i][j]=1;  
  17.             }  
  18.         }  
  19.     }  
  20.   
  21.     int i = nLen, j = nCapacity;  
  22.     while(i > 0 && j > 0)  
  23.     {  
  24.         if(Path[i][j] == 1)  
  25.         {  
  26.             cout << Weight[i-1] << " ";  
  27.             j -= Weight[i-1];  
  28.         }  
  29.         else  
  30.             i--;  
  31.     }  
  32.     cout << endl;  
  33.   
  34.     int nRet = Table[nLen][nCapacity];  
  35.     DestroyTwoDimArray(Table,nLen+1);   //銷燬二維數組  
  36.     DestroyTwoDimArray(Path,nLen+1);    //銷燬二維數組  
  37.     return nRet;  
  38. }  


//時間複雜度O(VN),不考慮路徑空間複雜度爲O(V),考慮路徑空間複雜度爲O(VN)

[cpp]   view plain copy
  1. int Package02_Compress(int Weight[], int Value[], int nLen, int nCapacity)  
  2. {  
  3.     int * Table = new int [nCapacity+1];  
  4.     memset(Table,0,(nCapacity+1)*sizeof(int));  
  5.   
  6.     int** Path = NULL;  
  7.     CreateTwoDimArray(Path,nLen+1,nCapacity+1);     //建立二維數組  
  8.   
  9.     for(int i = 0; i < nLen; i++)  
  10.     {  
  11.         for(int j = Weight[i]; j <=nCapacity; j++)  
  12.         {  
  13.             if(Table[j] < Table[j-Weight[i]]+Value[i])  
  14.             {  
  15.                 Table[j] = Table[j-Weight[i]]+Value[i];  
  16.                 Path[i+1][j] = 1;  
  17.             }  
  18.         }     
  19.     }  
  20.   
  21.     int i = nLen, j = nCapacity;  
  22.     while(i > 0 && j > 0)  
  23.     {  
  24.         if(Path[i][j] == 1)  
  25.         {  
  26.             cout << Weight[i-1] << " ";  
  27.             j -= Weight[i-1];  
  28.         }  
  29.         else  
  30.             i--;  
  31.     }  
  32.     cout << endl;  
  33.   
  34.     int nRet = Table[nCapacity];      
  35.     DestroyTwoDimArray(Path,nLen+1);    //銷燬二維數組  
  36.     delete [] Table;  
  37.     return nRet;  
  38. }  


測試代碼:

[cpp]   view plain copy
  1. int main()  
  2. {  
  3.     int Weight[] = {3,2,5,1,6,4};  
  4.     int Value[] =  {6,5,10,2,16,8};  
  5.     int nCapacity = 10;  
  6.     cout << Package02(Weight,Value,sizeof(Weight)/sizeof(int),nCapacity) << endl;  
  7.     cout << Package02_Compress(Weight,Value,sizeof(Weight)/sizeof(int),nCapacity) << endl;  
  8.     return 0;  
  9. }  
相關文章
相關標籤/搜索