01背包問題及空間優化


一、題目描述:

有n件物品,每件物品占用的空間為w[i], 價值為p[i]。

有容量為 V 的背包。求在容量允許的范圍下,背包裝入物品的最大價值。

 

用dp[i][v] 表示 用剩余容量為v的背包,來裝前i件物品,可以達到的最大價值。

那么 dp[i][0] = 0;

在當前為i,v的情況下,考察第i件物品。有兩種情況。

1、如果i物品的體積大於v,根本裝不下了。沒得選,只能放棄。則最大價值不變。相當於,用前i-1件物品來填滿v。dp[i][v] = dp[i-1][v];

2、如果i物品的體積不大於v,可以選擇裝入i或者放棄i。

如果裝入i,則剩余的容量變為v-w[i]。整個問題變為,先把i裝入,再用v-w[i]的空間去裝前i-1件物品。 dp[i][v] = dp[i-1][v-w[i]] + p[i]

如果放棄i,則dp[i][v] 依然是dp[i-1][v]。和情況1相同。

 

狀態轉移方程就出來了:

if( v >= w[i] )  dp[i][v] = max { dp[i-1][v] , dp[i-1][ v-w[i] ] + p[i] }
else    dp[i][v] = dp[i-1][v]

 

代碼如下:

int main()
{
    int n = 5;
    int sum = 12;

    int w[5] = {5, 4, 7 ,2 ,6};
    int p[5] = {12,3,10, 3, 6};
/*
    dp[i][V] 表示前i個,放入體積為V的背包內,獲得的最大收益。

    dp[0][0] = {0};        dp[i][V] = max{ dp[i-1][V], dp[i][V-w[i]] + p[i] }
*/
    int dp[6][13];

    int i,v;

    memset(dp,0,sizeof(dp));

    for(i = 1; i <= n ; i++)
    {
        //for(v = w[i-1];v<=sum;v++)    這是錯的!因為如果v < w[i]的話,dp[i][v]等於dp[i-1][V]。
        for(v = 1;v <= sum; v++)
        {
            if(v >= w[i-1])    dp[i][v] = max_2( dp[i-1][v-w[i-1]] + p[i-1],dp[i-1][v] );
            else            dp[i][v] = dp[i-1][v];
        }
    }

     for(i = 1; i <= n ; i++)
    {
        for(v = 0;v<=sum;v++)
        {
            printf("%2d ",dp[i][v]);
        }
        printf("\n");
    }
    return 0;
}

 

當V=0 和 1時,任何物品都不能放下,所以 前兩列的數都是0.

當v=2時,只能放下第四件物品,所以前三件的時候,依然是0,第四件以后,開始變化。

 

二、空間優化

注意到每次求解 dp[i][v]只用到了上一行(i-1)的值:dp[i-1][v]  和 dp[i-1][ v-w[i] ]。考慮用dp[v]來表示之前dp[i][v]對應的狀態。

第i次循環開始之前,dp [ j ] = dp [ i-1 ][ j ] ( 0<= j <= V )

第i次循環結束之后,dp [ j ] = dp [ i ][ j ]    ( 0<= j <= V )

那么在計算第i次循環的dp值時,兩種選擇變為下面的情況。

如果放棄物品i,即dp[i][v] = dp[i-1][v],dp[v]保持不變即可。

如果放入物品i,即dp[i][v] = dp[i-1][ v-w[i] ], 很幸運,當前的dp[v-w[i]]就是dp[i-1][v-w[i]]。

 

OK,代碼出來了。

for (i=0;i<n;i++)
{
    for(v = 0; v <= sum; v++)
    {
        if( v < w[i])    
            dp[v] = dp[v] ;//  第i-1輪的值直接更新到了第i輪
        else  
            dp[v] = max{ dp[v],dp[ v-w[i] ] };
    }
} 

 

實際運行結果是不對的。

仔細觀察,有個問題出現了。我們必須保證,在計算dp[v]的時候,dp[ v -w[i] ]依然等於dp[i-1][ v-w[i] ]。

但是看代碼中,明顯不是這樣。因為v是從小到大計算,dp[ v-w[i]] 已經在 dp[v]之前被計算過了。我們用到的dp[ v-w[i] ]是第i次循環計算出來的值。

我們的代碼相當於在執行:dp[ i ][ v ] = max{ dp[ i-1 ][ v ],dp[ i ][ v-w[i] ] };   (注意是dp[ i ][ v-w[i] ]!)

 

為了保證先計算dp[v],需要顛倒一下計算順序。

最終版本如下:

int main()
{
    int w[5] = {5, 4, 7 ,2 ,6};
    int p[5] = {12,3,10, 3, 6};

    int i,v,n = 5;
    int sum = 12;

    int dp[13];

    memset(dp,0,sizeof(dp));

    for(i=0;i<n;i++)
    {
        for(v=sum;v>=w[i];v--)
        {
            dp[v] = max_2( dp[v], dp[v-w[i]]+p[i] );
        }
    }
    
    for(i = 0;i<=sum;i++)
    {
        printf("%d ",dp[i]);
    }
    return 0;
}

第一次循環之前,i=0,dp[v] 全體為0 . 

每一次循環得到的值,和dp[i][v]的第i行是一致的。

 

記錄自己的一些理解和思考。不能保證代碼完全正確,因為沒有經過大量數據測試。

歡迎指正!

 

根據背包九講的第一講,整理擴充下。自己思考完才能看懂了。。。


免責聲明!

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



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