一、0/1揹包問題算法基本思想
0/1揹包問題是經典的動態規劃問題。
問題基本描述爲:給你體積爲V的揹包,在物品G1,G2,G3...(這些物品所對應的體積分別爲W1,W2,W3...價值分別爲P1,P2,P3...)中選擇一些放到揹包裏,使得在不超過揹包體積的情況下,揹包內物品的價值總和爲最大。
一句話:決定一件物品要不要放進揹包的條件是這件物品放進去所帶來的總體收益是大於還是小於不放進去的總體收益,也就是對應的動態規劃方程爲:f(i,v) = max{ f(i-1,v) , f(i-1,v-w[i]) + p[i] },其中f(i,v)表示在體積爲V的揹包裏放i件物品。這個方程的含義就是 在v體積的揹包內,放i-1件物品(不放i物品)所帶來的收益爲f(i-1,v)。 在v體積的揹包內,如果要放i物品,那麼剩下的體積只有v-w[i],在這麼多體積內放i-1件物品所帶來的總體收益再加上i物品帶來的 收益 就是f(i-1, v-w[i])。那麼在v體積內可以放i件物品的時候最大收益就是這以上兩種情況的較大者。
二、0/1揹包問題的數據結構
w[i] 一維數組: 表示i物品所佔體積
p[i] 一維數組: 表示i物品的收益
三、0/1揹包問題過程圖解
0/1揹包問題確實難畫圖解,網上的現成的也少,我就不好意思省略了。
四、算法源代碼
1.0/1揹包遞歸算法
#include<cstdio> #define MAXN 20 //物品1,2...5的價值 int p[] = {0,6,3,5,4,6}; //物品1,2...5的體積 int w[] = {0,2,2,6,5,4}; //遞歸算法: 表示當揹包總共容量爲V的情況下,放i件物品所能獲得的最大價值 int f(int i,int V); //回溯求最優解向量 void traceback(int x[],int n,int ft[][MAXN],int V); //計算最大值 int max(int a,int b); //程序執行入口 int main() { printf("Using a recursive algorithm:\n"); int maxValue = f(5,10); printf("when the count of goods is %d and the total volume of the bag is %d,\nthe maxValue is %d\n\n",5,10,maxValue); return 0; } int max(int a,int b){ return a>b?a:b; } //遞歸實現 p[] = {0,6,3,5,4,6}; w[] = {0,2,2,6,5,4}; 注意p,w中0只是拿來填充index = 0的時候沒什麼意義 int f(int i,int V) { //當揹包裏面物品爲0個的時候,價值當然爲0 if(i==0) return 0; //當所放物品的體積超過揹包所提供最大體積 //當然不能考慮把這個物品放入揹包的情況 if (V < w[i]) return f(i-1,V); //當情況不是上面那樣的時候 //就要考慮把當前 i 物品放入揹包 else return max(f(i-1,V),f(i-1,V-w[i])+p[i]); } //回溯尋找最優解向量 void traceback(int x[],int n,int ft[][MAXN],int V) { //基本思路就是在最優解成立情況下,如果i物品被放進揹包(即x[i] = 1) //那麼ft[i][V]就是對應的最優子結構,這樣給剩下物品提供的總體積減少w[i] //如果i物品不放進揹包那麼x[i] = 0,當然給剩下物品的總體積不變 for(int i=1;i<=n;i++){ //這一步比較就是來說明i物品有沒有放進去 //如果放進去f[i][V]和f[i-1][V]肯定是不會相同的 if(ft[i][V]==ft[i-1][V]) x[i] = 0; else{ x[i] = 1; V-=w[i]; } } }
2.0/1揹包遞推算法(加回溯求解向量)
#include<cstdio> #define MAXN 20 //物品1,2...5的價值 int p[] = {0,6,3,5,4,6}; //物品1,2...5的體積 int w[] = {0,2,2,6,5,4}; //遞推算法: void knapsack(int n,int V,int ft[][MAXN],int p[],int w[]); //回溯求最優解向量 void traceback(int x[],int n,int ft[][MAXN],int V); int ft[MAXN][MAXN]; //計算最大值 int max(int a,int b); //程序執行入口 int main() { printf("Using a recursion algorithm:\n"); knapsack(5,10,ft,p,w); printf("when the count of goods is %d and the total volume of the bag is %d,\nthe maxValue is %d\n",5,10,ft[5][10]); int x[MAXN]; traceback(x,5,ft,10); printf("the best answer is ("); for(int i=1;i<=5;i++) printf("%d,",x[i]); printf(")\n"); return 0; } int max(int a,int b){ return a>b?a:b; } //遞推算法 p[] = {0,6,3,5,4,6}; w[] = {0,2,2,6,5,4}; 注意p,w中0只是拿來填充index = 0的時候沒什麼意義 void knapsack(int n,int V,int ft[][MAXN],int p[],int w[]) { //初始化 for(int i=0;i<=V;i++){ ft[0][i] = 0; } for(int i=1;i<=n;i++) { for(int j=1;j<=V;j++) { if(j<w[i]) ft[i][j] = ft[i-1][j]; else ft[i][j] = max(ft[i-1][j],ft[i-1][j-w[i]]+p[i]); } } } //回溯尋找最優解向量 void traceback(int x[],int n,int ft[][MAXN],int V) { //基本思路就是在最優解成立情況下,如果i物品被放進揹包(即x[i] = 1) //那麼ft[i][V]就是對應的最優子結構,這樣給剩下物品提供的總體積減少w[i] //如果i物品不放進揹包那麼x[i] = 0,當然給剩下物品的總體積不變 for(int i=1;i<=n;i++){ //這一步比較就是來說明i物品有沒有放進去 //如果放進去f[i][V]和f[i-1][V]肯定是不會相同的 if(ft[i][V]==ft[i-1][V]) x[i] = 0; else{ x[i] = 1; V-=w[i]; } } }