這個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; }