一、問題描述:
有n 個物品,它們有各自的重量和價值,現有給定容量的背包,如何讓背包里裝入的物品具有最大的價值總和?
二、動態規划的原理及過程:
eg:number=4,capacity=8
i |
1 |
2 |
3 |
4 |
w(體積) |
2 |
3 |
4 |
5 |
v(價值) |
3 |
4 |
5 |
6 |
1.原理
動態規划是把大問題拆分成小問題,通過尋找大問題與小問題的遞推關系,解決一個個小問題,最終達到解決原問題的效果。動態規划則通過填寫表把所有已經解決的子問題答案紀錄下來,在新問題里需要用到的子問題可以直接提取,避免了重復計算,從而節約了時間,所以在問題滿足最優性原理之后,用動態規划解決問題的核心就在於填表,表填寫完畢,最優解也就找到。
2.過程
(1)用v[i]表示物品價值,w[i]表示物品重量。定義狀態dp[i][j]以j為容量為放入前i個物品(按i從小到大的順序)的最大價值。
(2)初始化邊界條件,V(0,j)=V(i,0)=0;
(3)對於每一個物品,有兩種選擇方法,能裝下和不能裝下。
第一,包的容量比該商品體積小,裝不下,此時的價值與前i-1個的價值是一樣的,即V(i,j)=V(i-1,j);
第二,還有足夠的容量可以裝該商品,但裝了也不一定達到當前最優價值,所以在裝與不裝之間選擇最優的一個,即V(i,j)=max{ V(i-1,j),V(i-1,j-w(i))+v(i) }其中V(i-1,j)表示不裝,V(i-1,j-w(i))+v(i) 表示裝了第i個商品,背包容量減少w(i)但價值增加了v(i);
(4)得出遞推關系式:
① j<w(i) V(i,j)=V(i-1,j) //只是為了好理解,可以不用寫,不會影響結果。
② j>=w(i) V(i,j)=max{ V(i-1,j),V(i-1,j-w(i))+v(i) }
3.圖解
1.初始狀態將邊界初始化為0。j表示背包的的重量,i表示第i個物品,填表方式為一行一行的填,每次填寫的時候取 V(i-1,j),V(i-1,j-w(i))+v(i)中的較大值
2.將數組更新完則是這樣的情況。隨便取一個舉例子,比如第二行第五個的7。它是由3,v[2][2]+4=7中的較大值比較出來的。
三、代碼
對於0-1背包的代碼可以有很多種形式,只要是狀態轉移方程正確即可。用二維數組來存儲表格代碼如下。
#include <iostream> using namespace std; int w[105], val[105]; int dp[105][1005]; int main() { int t, m, res=-1; cin >> t >> m; for(int i=1; i<=m; i++) cin >> w[i] >> val[i]; for(int i=1; i<=m; i++) //物品 for(int j=t; j>=0; j--) //容量 { if(j >= w[i]) dp[i][j] = max(dp[i-1][j-w[i]]+val[i], dp[i-1][j]); else //只是為了好理解 dp[i][j] = dp[i-1][j]; } cout << dp[m][t] << endl; return 0; }
四、空間優化
由上面的圖可以看出來,每一次V(i)(j)改變的值只與V(i-1)(x) {x:1...j}有關,V(i-1)(x)是前一次i循環保存下來的值;
因此,可以將V縮減成一維數組,從而達到優化空間的目的,狀態轉移方程轉換為 B(j)= max{B(j), B(j-w(i))+v(i)};
並且,狀態轉移方程,每一次推導V(i)(j)是通過V(i-1)(j-w(i))來推導的,所以一維數組中j的掃描順序應該從大到小(capacity到0),否者前一次循環保存下來的值將會被修改,從而造成錯誤。
代碼:
//求解將哪些物品裝入背包可使這些物品的重量總和不超過背包承重量t,且價值總和最大。 #include <stdio.h> #include <string.h> int f[1010],w[1010],v[1010];//f記錄不同承重量背包的總價值,w記錄不同物品的重量,v記錄不同物品的價值 int max(int x,int y){//返回x,y的最大值 if(x>y) return x; return y; } int main(){ int t,m,i,j; memset(f,0,sizeof(f)); //總價值初始化為0 scanf("%d %d",&t,&m); //輸入背包承重量t、物品的數目m for(i=1;i<=m;i++) scanf("%d %d",&w[i],&v[i]); //輸入m組物品的重量w[i]和價值v[i] for(i=1;i<=m;i++){ //嘗試放置每一個物品 for(j=t;j>=w[i];j--){//倒敘是為了保證每個物品都使用一次 f[j]=max(f[j-w[i]]+v[i],f[j]); //在放入第i個物品前后,檢驗不同j承重量背包的總價值,如果放入第i個物品后比放入前的價值提高了,則修改j承重量背包的價值,否則不變 } } printf("%d",f[t]); //輸出承重量為t的背包的總價值 printf("\n"); getch(); return 0; }