簡單描述
0-1背包問題描述如下:
有一個容量為V的背包,和一些物品。這些物品分別有兩個屬性,體積w和價值v,每種物品只有一個。要求用這個背包裝下價值盡可能多的物品,求該最大價值,背包可以不被裝滿。因為最優解中,每個物品都有兩種可能的情況,即在背包中或者不存在(背 包中有0個該物品或者 1個),所以我們把這個問題稱為0-1背包問題。
0-1背包問題狀態轉移方程
用dp[i][j]表示前i個物品在總體積不超過j的情況下,放到背包里的最大價值。由此可以推出狀態轉移方程:
dp[0][j] = 0;
dp[i][j] = max{dp[i-1][j-v[i]] + w[i],dp[i-1][j]};
上面的式子應該很好理解,當第i物品的體積小於當前剩余的體積,則說明可以裝入背包,那么dp[i][j] = dp[i-1][j-v[i]]+w[i]。反之就是不能轉入背包,dp[i][j] = dp[i-1][j]。
0-1背包問題實現算法1
#include <iostream>
using namespace std;
#define MAXSIZE 100
int w[MAXSIZE]; int v[MAXSIZE]; int maxv; int n; int dp[MAXSIZE][MAXSIZE]; int max(int a, int b) { if (a > b) return a; else return b; } int main() { cin >> n >> maxv; for (int i = 1; i <= n; i++) { cin >> w[i] >> v[i]; } for (int i = 0; i <= maxv; i++) dp[0][i] = 0; for (int i = 1; i <= n; i++) { //只有當j >= w[i],dp[i][j]才能進行選取最大值 for (int j = maxv; j >= w[i]; j--) { dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i]); } //當j < w[i],說明第i個物品是不能轉入背包的,故dp[i][j] = dp[i-1][j] for (int j = w[i] - 1; j >= 0; j--) dp[i][j] = dp[i - 1][j]; } cout << dp[n][maxv] << endl; return 0; }
看到這里,還沒完,哈哈!下面介紹一下對dp的優化,我們發現這里的dp是一個二維數組,其實dp完全可以用一維數組表示。為啥子???
0-1背包問題實現算法2
看這里:可以發現0-1背包的狀態轉移方程 dp[i][j] = max{dp[i-1][j-w[i]]+v[i],dp[i-1][j]}的特點,當前狀態僅依賴前一狀態的剩余體積與當前物品體積v[i]的關系。根據這個特點,我們可以將dp降到一維即dp[j] = max{dp[j],dp[j-w[i]]+v[i]}。從這個方程中我們可以發現,有兩個dp[j],但是要區分開。等號左邊的dp[j]是當前i的狀態,右邊中括號內的dp[j]是第i-1狀態下的值。
所以為了保證狀態的正確轉移,我們需要先更新等號左邊中的dp[j](當前狀態的dp[j])。
#include <iostream>
using namespace std;
#define MAXSIZE 100
int w[MAXSIZE]; int v[MAXSIZE]; int maxv; int n; int dp[MAXSIZE]; int max(int a, int b) { if (a > b) return a; else return b; } int main() { cin >> n >> maxv; for (int i = 1; i <= n; i++) { cin >> w[i] >> v[i]; } for (int i = 0; i <= maxv; i++) dp[i] = 0; for (int i = 1; i <= n; i++) { //只有當j >= w[i],dp[j]才能進行選取最大值,否則dp[j]將不作更新,等於dp[i-1][j]。 for (int j = maxv; j >= w[i]; j--) { dp[j] = max(dp[j], dp[j - w[i]] + v[i]); } } cout << dp[maxv] << endl; return 0; }
比較上面的兩個算法可以發現,它們的時間復雜度都是O(n*maxv),只有空間復雜度發生變化。后者的空間復雜度得到了優化。
拓展0-1背包問題
哈哈,還沒完,繼續0-1背包問題,如果在上面的問題加上一個限制條件,所選擇的物品必須恰好裝滿背包,否則輸出-1。
同樣的給出兩種算法,它們的時間復雜度都是一樣的,只不過是空間復雜度不同。
空間復雜度為O(n*maxv)的算法
#include <iostream>
using namespace std;
#define MAXSIZE 100
int w[MAXSIZE]; int v[MAXSIZE]; int maxv; int n; int dp[MAXSIZE][MAXSIZE]; int max(int a, int b) { if (a > b) return a; else return b; } int main() { cin >> n >> maxv; for (int i = 1; i <= n; i++) { cin >> w[i] >> v[i]; } //初始化,當容積為0時,即不能裝入,最大價值即為0 for (int i = 1; i <= n; i++) { dp[i][0] = 0; } //初始化為-1,表示沒有裝滿 for (int i = 0; i <= n; i++) for (int j = 1; j <= maxv; j++) dp[i][j] = -1; for (int i = 1; i <= n; i++) { for (int j = maxv; j >= w[i]; j--) { //dp[i - 1][j - w[i]] != -1表示容積為j - w[i]時沒有裝滿,所以當容積為j,裝w[i]時一定不能裝滿 //dp[i - 1][j - w[i]] + v[i] > dp[i-1][j]表示裝入物品i時簽好裝滿並且總價值比前i-1個物品的總價值要大 if (dp[i - 1][j - w[i]] != -1 && dp[i - 1][j - w[i]] + v[i] >= dp[i - 1][j]) dp[i][j] = dp[i - 1][j - w[i]] + v[i]; } for (int j = w[i] - 1; j >= 1; j--) dp[i][j] = dp[i - 1][j]; } cout << dp[n][maxv] << endl; return 0; }
空間復雜度為O(maxv)的算法
#include "stdafx.h"
#include <iostream>
using namespace std; #define MAXSIZE 100 int w[MAXSIZE]; int v[MAXSIZE]; int maxv; int n; int dp[MAXSIZE]; int max(int a, int b) { if (a > b) return a; else return b; } int main() { cin >> n >> maxv; for (int i = 1; i <= n; i++) { cin >> w[i] >> v[i]; } //初始化,當容積為0時,即不能裝入,最大價值即為0 dp[0] = 0; //初始化為-1,表示沒有裝滿 for (int j = 1; j <= maxv; j++) dp[j] = -1; for (int i = 1; i <= n; i++) for (int j = maxv; j >= w[i]; j--) { if (dp[j - w[i]] != -1 && dp[j - w[i]] + v[i] >= dp[j]) dp[j] = dp[j - w[i]] + v[i]; } cout << dp[maxv] << endl; return 0; }
從上面的算法我們發現,這里的狀態轉移方程和0-1背包問題的狀態轉移方程是一樣一樣滴,只不過是初試狀態發生了一點改變。
呵呵,到這里0-1背包問題先結束了,后面會繼續介紹更加復雜的背包問題。
ps:走一步,學一步,總結一步,路就不會太遠,目標也不會遙不可及。