DP- 01揹包問題

 

這個01揹包 , 理解了一天才勉強懂點 , 寫個博客  (  推薦   http://blog.csdn.net/insistgogo/article/details/8579597)ios

 

題目 :數組

  有N件物品和一個容量爲V的揹包。第i件物品的費用是c[i],價值是w[i]。求解將哪些物品裝入揹包可以使價值總和最大。優化

 

分析一下 :spa

  面對一件物品 , 我有兩種選擇 , 放或者不放 , 若是不放 , 則最大價值是 前 n - 1 件物品放入容量爲 j 的揹包 , 若是放 , 則最大價值是將前 n - 1 件物品放入剩餘容量爲 j - c[j] , 此時的.net

價值是 前 n - 1 件物品放入剩餘容量爲 v - c[ j ] 的價值加上放入該物品的價值 .......一直此過程 , 最後的最大價值便是 c[ n ] [ v ] 。3d

 

( 偷一個圖 ):blog

  

   要怎樣去實現這個代碼呢 ? 先用最好的理解的二維數組去解決這個問題 , 兩個維度分別存的是 物品的個數以及 當前揹包的總容量 , 由於我第一層 for 是從 遍歷全部的物品 , 即面對某個物品 , 第二層 for  我是遍歷體積 , 每次讓體積 + 1 , 這樣就會產生一種 什麼效果呢 ? 在面對每個物品時 , 我全部的體積都去試一下 , 這個過程 從最終的意義上考慮實際都是在爲最後 揹包容量滿的時候服務 , 這個過程 也就叫作構建最優子結構的過程 , 而且當前產生的結果 , 對後面不會有任何影響 , 也就是無後效性 。內存

  ( 關於這個代碼 , 我感受有一個地方很精髓 , 就是 當面對 一號 物品時 , 個人狀態轉移方程考慮的是前 i - 1 個物品 , 即沒有物品的時候 , 此時我創建的數組就能夠在 i = 0  的位置留出地方 , 而且藉助 memset 將這些位置都給 0 )ci

  (還有 這個的揹包容量也能夠從 V 開始遍歷 直到 1 結束 , 則會打出另外一張表)博客

 

代碼示例 :

  

// 感受 DP 中的揹包問題 就是一個製做表格的過程
// 所製做的表的大小是 n * v 

#include <iostream>
#include <algorithm>
using namespace std ;

int dp[10][500] ;   // 定義爲全局變量 會被初始化爲 0
int weight[15] ;
int value[15] ;

int main ( ) {
    int n , v ;

    cin >> n >> v ;
    for ( int i = 1 ; i <= n ; i++ ) {
        cin >> weight[i] >> value[i] ;
    }

    for ( int i = 1 ; i <= n ; i++ ) {
        for ( int j = 1 ; j <= v ; j++ ) {
            if ( weight[i] <= j )
                dp[i][j] = max ( dp[i-1][j] , dp[i-1][j-weight[i]] + value[i] ) ;
            else
                dp[i][j] = dp[i-1][j] ; // 若是當前面對此物品揹包中不能再放東西 ,但表的這一欄又不能空 ,
                                        // 因此填的數 是這個體積下,沒有此物品的體積
        }
    }

    cout << dp[n][v] << endl ;

    return 0 ;
}

 

 

二 . 優化空間複雜度

  用二維數組去寫的話 , 複雜度爲 O (n*v) , 在數據大的時候直接超內存 , 因此能夠藉助一維數組 , 將複雜度優化爲 O (n)

  貼上個人代碼 :

    

// 感受 DP 中的揹包問題 就是一個製做表格的過程
// 所製做的表的大小是 n * v
#include <iostream>
#include <algorithm>
using namespace std ;

int dp[1500] ;   // 定義爲全局變量 會被初始化爲 0
int weight[15] ;
int value[15] ;

int main ( ) {
    int n , v ;

    cin >> n >> v ;
    for ( int i = 1 ; i <= n ; i++ ) {
        cin >> weight[i] >> value[i] ;
    }

    for ( int i = 1 ; i <= n ; i++ ) {
        for ( int j = v ; j >= 1 ; j-- ) {
            if ( weight[i] <= j )
                dp[j] = max ( dp[j] , dp[j-weight[i]] + value[i] ) ;  // 當面對一個物品時 , 此時數組所對應的便是前 i - 1 的價值 
        }
    }

    cout << dp[v] << endl ; 

    return 0 ;
}

 

 

 

// 用一維數組作 , 感受和數字三角形的題很像 , 從底下遞推 。此揹包問題就是 , 在面對每一個物品時,不斷地更新數組中的數據 , 而且在面對每一個物品時 , 其體積是從 V 到 1 遞推  

// 用一維數組作 , 當面對一個物品 , 在這個位置上 ,數組所存的數便是 在該體積下 , 前 i - 1 件物品的最大價值

 

有一個問題 : 體積爲何要是逆序遞推呢 ?

而後附上我程序的運行結果 :

  

 

三 .  初始化的細節問題 

  在最優解得揹包問題 , 有兩種問法 ;

 1 . 在不超過揹包容量時 , 如何裝會得到最大價值 ?

 2 .當揹包剛好裝滿時 , 得到的最大價值是多少 ?

 

  兩種問法的惟一區別就在於就在於揹包是否裝滿 , 這兩種問法的實現僅僅區別於對數組元素的初始化 。

  初始化 f 數組就表示 , 在沒有放入任何物品時揹包的合法狀態 。

  對於剛好裝滿的狀況 , 我應對此二維數組的 dp[ i ] [ 0 ]  初始化爲 0 , 由於我在面對一個物品時 , 我此時揹包的容量爲 0 , 不能再聽任何東西 , 即爲剛好裝滿的時候 , 將此 dp 數組其他位置所有初始爲  負無窮 , 其他過程所有同上 ... 最後時 輸出剛好的最優解 即 dp[ n ][ v ] 。

  對於不超過揹包容量的狀況 , 我只須要將數組中的元素所有初始爲 0 。若是揹包並不是 要所有裝滿 , 那麼任何揹包都有一個合法解 , 即什麼也不裝 。

 

 剛好裝滿的二維數組代碼 :

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std ;

int dp[10][500] ;   // 定義爲全局變量 會被初始化爲 0
int weight[15] ;
int value[15] ;

int main ( ) {
    int n , v ;
    int i , j;

    cin >> n >> v ;
    memset ( dp , 0x8f , sizeof(dp) ) ;  // 將數組所有元素初始化爲 負無窮
    for ( i = 0 ; i <= n ; i++ ) {
        dp[i][0] = 0 ;
    }

    for ( i = 1 ; i <= n ; i++ ) {
        cin >> weight[i] >> value[i] ;
    }

    for ( i = 1 ; i <= n ; i++ ) {
        for ( j = 1 ; j <= v ; j++ ) {
            if ( weight[i] <= j ) {
                dp[i][j] = max ( dp[i-1][j] , dp[i-1][j-weight[i]] + value[i] ) ;
            }
            else
                dp[i][j] = dp[i-1][j] ;
        }
    }

    cout << dp[n][v] << endl ;
    return 0 ;
}

 

剛好裝滿的一維數組的代碼 :

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std ;

int dp[1500] ;   // 定義爲全局變量 會被初始化爲 0
int weight[15] ;
int value[15] ;

int main ( ) {
    int n , v ;

    memset ( dp , 0x8f , sizeof(dp) ) ;
    dp[0] = 0 ;
    cin >> n >> v ;
    for ( int i = 1 ; i <= n ; i++ ) {
        cin >> weight[i] >> value[i] ;
    }

    for ( int i = 1 ; i <= n ; i++ ) {
        for ( int j = v ; j >= weight[i] ; j-- ) {
            dp[j] = max ( dp[j] , dp[j-weight[i]] + value[i] ) ;  // 當面對一個物品時 , 此時數組所對應的便是前 i - 1 的價值
        }
    }

    cout << dp[v] << endl ;

    return 0 ;
}

 

 附上利用機器打得表 :

  

 

 

四 . 一個常數的優化

  在用一維數組 , 作揹包問題時 , 揹包容量是從  v 到 1 遍歷 ,但實際上只須要遍歷到 weight[ i ] 。

代碼示例 :

  

#include <iostream>
#include <algorithm>
using namespace std ;

int dp[1500] ;   // 定義爲全局變量 會被初始化爲 0
int weight[15] ;
int value[15] ;

int main ( ) {
    int n , v ;

    cin >> n >> v ;
    for ( int i = 1 ; i <= n ; i++ ) {
        cin >> weight[i] >> value[i] ;
    }

    for ( int i = 1 ; i <= n ; i++ ) {
        for ( int j = v ; j >= weight[i] ; j-- ) {    //////////  修改處
            dp[j] = max ( dp[j] , dp[j-weight[i]] + value[i] ) ;  // 當面對一個物品時 , 此時數組所對應的便是前 i - 1 的價值
        }
    }

    cout << dp[v] << endl ;

    return 0 ;
}

 

揹包的路徑輸出(板子)

int n,m;
int v[MAX],w[MAX];
int dp[MAX];
bool path[MAX][MAX];
int V;
void solve()
{
    memset(dp,0,sizeof(dp));
   memset(path,false,sizeof(path));
   for(int i=0;i<n;i++)
   {
       for(int j=V;j>=w[i];j--)
        if(dp[j-w[i]]+v[i]>dp[j])
        {
            dp[j]=dp[j-w[i]]+v[i];
            path[i][j]=true;//cout<<i<<j<<endl;
        }
   }
   cout<<dp[V]<<endl;
   int ans[MAX];
   int k=0;
   for(int i=n-1;i>=0;i--)
   {
       if(path[i][V]){
        ans[++k]=i;
        V-=w[i];
       }
   }
   //輸出所選擇的物品
   for(int i=k;i>0;i--)
     cout<<ans[i]<<endl;
}
相關文章
相關標籤/搜索