背包問題---01背包(原理,偽代碼,編程實現)


 01背包是在M件物品取出若干件放在空間為W的背包里,每件物品的體積為C1,C2,…,Cn,與之相對應的價值為W1,W2,…,Wn.求解將那些物品裝入背包可使總價值最大。

        動態規划(DP):

        1) 子問題定義:F[i][j]表示前i件物品中選取若干件物品放入剩余空間為j的背包中所能得到的最大價值。

        2) 根據第i件物品放或不放進行決策

                                                  (1-1)

        其中F[i-1][j]表示前i-1件物品中選取若干件物品放入剩余空間為j的背包中所能得到的最大價值;

        而F[i-1][j-C[i]]+W[i]表示前i-1件物品中選取若干件物品放入剩余空間為j-C[i]的背包中所能取得的最大價值加上第i件物品的價值。

        根據第i件物品放或是不放確定遍歷到第i件物品時的狀態F[i][j]。

        設物品件數為N,背包容量為V,第i件物品體積為C[i],第i件物品價值為W[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 k←1 to V  
  8.   
  9.         F[i][k] ← F[i-1][k]  
  10.   
  11.         if(k >= C[i])  
  12.   
  13.             then F[i][k] ← max(F[i][k],F[i-1][k-C[i]]+W[i])  
  14.   
  15. return F[N][V]  

        以上偽代碼數組均為基於1索引,及第一件物品索引為1。時間及空間復雜度均為O(VN)

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

表1-1背包問題數據表

物品號i 1 2 3 4 5 6
體積C 2 3 1 4 6 5
價值W 5 6 5 1 19 7

 

表1-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 5 5 5 5 5 5 5 5 5
2 0 5 6 6 11 11 11 11 11 11 11
3 0 5 5 10 11 11 16 16 16 16 16
4 0 5 5 10 11 11 16 16 16 16 17
5 0 5 5 10 11 11 19 24 24 29 30
6 0 5 5 10 11 11 19 24 24 29 30

 

         很多文章講背包問題時只是把最大價值求出來了,並沒有把所選的是哪些物品找出來。本人在學習背包問題之前遇到過很多的類似問題,當時也是只求得了最大價值或最大和,對具體哪些物品或路徑等細節也束手無策。再次和大家一起分享細節的求法。

        根據算法求出的最大價值表本身其實含有位置信息,從F[N][V]逆着走向F[0][0],設i=N,j=V,如果F[i][j]==F[i-1][j-C[i]]+W[i]說明包里面有第i件物品,同時j -= C[i],不管F[i][j]與F[i-1][j-C[i]]+W[i]相不相等i都要減1,因為01背包的第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-1][j-C[i]]+W[i])  
  8.   
  9.         then Print W[i]  
  10.   
  11.              j←j-C[i]  
  12.   
  13.     i←i-1  

         當然也可以定義一個二維數組Path[N][V]來存放背包內物品信息,開始時Path[N][V]初始化為0,當 F[i][j]==F[i-1][j-C[i]]+W[i]時Path[i][j]置1。最后通過從Path[N+1][V+1]逆着走向Path[0][0]來獲取背包內物品。其中Path[0][]與Path[][0]為邊界。

        加入路徑信息的偽代碼如下:

[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-1][k-C[i]]+W[i])  
  14.   
  15.             then F[i][k] ← F[i-1][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.     i←i-1  

    在時間及空間復雜度均為O(NV)的情況下,利用Path[][]的方法明顯比直接通過F[i][j]==F[i-1][j-C[i]]+W[i]來打印物品耗費空間,Path[][]需要額外的空間O(NV)但總空間復雜度不變仍為O(NV)。但下面要講到的O(V)的空間復雜度的方法卻不能利用關系式F [j]==F [j-C[i]]+W[i]而只能利用Path[][]進行標記.

 

接下來考慮如何壓縮空間,以降低空間復雜度。

時間復雜度為O(VN),空間復雜度將為O(V)

 

        觀察偽代碼可也發現,F[i][j]只與F[i-1][j]和F[i-1][j-C[i]]有關,即只和i-1時刻狀態有關,所以我們只需要用一維數組F[]來保存i-1時的狀態F[]。假設i-1時刻的F[]為{a0,a1,a2,…,av},難么i時刻的F[]中第k個應該為max(ak,ak-C[i]+W[i])即max(F[k],F[k-C[i]]+W[i]),這就需要我們遍歷V時逆序遍歷,這樣才能保證求i時刻F[k]時F[k-C[i]]是i-1時刻的值。如果正序遍歷則當求F[k]時其前面的F[0],F[1],…,F[K-1]都已經改變過,里面存的都不是i-1時刻的值,這樣求F[k]時利用F[K-C[i]]必定是錯的值。最后F[V]即為最大價值。

求F[j]的狀態方程如下:

                                           (1-2)

偽代碼如下:

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


同樣,怎么求路徑?

        利用前面講到的Path[][]標記,需空間消耗O(NV)。這里不能用F [j]==F [j-C[i]]+W[i]來判斷是因為一維數組並不能提供足夠的信息來尋找二維路徑。

        加入路徑信息的偽代碼如下:

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

打印路徑的偽代碼和前面未壓縮空間復雜度時的偽代碼一樣,這里不再重寫

這里給出第二種思路 

時間復雜度O(VN),不考慮路徑空間復雜度為O(V),考慮路徑空間復雜度為O(VN)

#include<iostream>
using namespace std;
#define Size 1111

int dp[Size];
int Path[Size][Size];
int Max(int x,int y)
{
    return x>y?x:y;
}
int Package01_Compress(int Weight[], int Value[], int goodsN, int maxWeight){
	int i,j;
    memset(dp,0,sizeof(dp));
    memset(Path,0,sizeof(Path));
		for(i=1;i<=goodsN;i++)
			for(j=maxWeight;j>=Weight[i];j--){	
            /* -----只求最大值----- */	
			/*	dp[j]=Max(   dp[j] ,  dp[j-Weight[i]]  +Value[i]  );*/
			/*------求最大值和路徑------*/ 
            if(dp[j] < dp[j-Weight[i]]+Value[i])  
            {  
                dp[j] = dp[j-Weight[i]]+Value[i];  
                Path[i][j] = 1;  
            }  
			/*--------------------------*/ 
			}
	return dp[maxWeight];		
    }
int main()
{
    int va[Size],vm[Size];
    
    int t,n,m;
    int i;
    cin>>t;
    while(t--)
    {
        cin>>n>>m; //n為個數,m為最大載重量
        for(i=1;i<=n;i++)
            cin>>va[i];
        for(i=1;i<=n;i++)
			cin>>vm[i];
		int myWhats=Package01_Compress(vm,va, n, m);
			printf("%d\n",myWhats);
		printf("-----取的重量如下(不超過%d):\n",m);
        int i2 = n, j2 = m;  
        while(i2 > 0 && j2 > 0)  
        {  
        if(Path[i2][j2] == 1)  
        {  
            cout << vm[i2] << " ";  
            j2 -= vm[i2];  
        }  
  
        i2--;  
        }  
    cout << endl;  	
	}
	return 0;
}
本文部分內容參考“背包九講”
這里

版權聲明:本文為博主原創文章,未經博主允許不得轉載。


免責聲明!

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



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