01揹包
前言
揹包問題本身已經是老生常談了,但是這依舊是一個十分經典的問題,初學動態規劃的朋友們肯定都繞不過它。網上已經有了很多十分優秀的揹包問題講解,譬如經典的《揹包九講》,但是我自己學了揹包之後發現一個問題就是理解不夠透徹,見到一個問題學一次,學一次就能刷幾道題。現在我自己重新做了一些算法題,遇到了簡單的01揹包依舊不知道怎麼做,於是就憑藉零散的記憶和自我理解,對這些問題進行了求解,在這裏把我的理解寫下來,一方面如果能夠幫助到其他人也算是一件善事,另一方面也算是一種成長記錄。
正文
01揹包的表述爲有n 個物品,它們有各自的重量和價值,現有給定容量的揹包,如何讓揹包裏裝入的物品具有最大的價值總和?
此處假設第i個物品的花費爲c[i],價值爲v[i];則對於任一個物品對問題的總貢獻只能是0或者是v[i],即取或者不取;設在當前最大揹包容量爲V的情況下,考慮第i個物品的貢獻值時的最優解爲
f[i][V]=max{ f[ i-1 ][ V ] , f[ i-1 ][ V-c[ i ] ]+v[ i ]}
式中f[i][V]表示i個物品在容量爲V的情況下的最優解。 有了動規方程,接下來就是確定平凡解。 容易知道當容量爲0時,不論物品的價值如何,最優解都是0,即f[i][0]恆等於0;另外當只有一個物品的時候,如果一個物品能放進揹包裏,那麼不論揹包容量有多大,最優解也只能爲這個物品的價值,即f[0][val]爲0或者v[0]。看到這裏可能會有一點抽象,那麼我們來進行一個簡單的模擬,假如揹包容量V=4,c[0]=2,v[0]=2;c[1]=1,v[1]=1.
由上面的分析可以得到如下表格:
i\v | 0 | 1 | 2 | 3 | 4 |
0 | 0 | 0 | 3 | 3 | 3 |
1 | 0 | 1 | 3 | 4 | 4 |
效率分析
時間複雜度上易知,需要遍歷整個表格,所以時間複雜度是O(n*V),V是揹包大小,n是物品個數。空間複雜度的上界是O(n*V)。一般來說當揹包容量達到10^5的時候空間複雜度就難以忍受了,但是時間上還有餘裕。那麼如何進行空間優化呢?
如果通過上面的例子求解和動規方程,聰明的你就能發現,下一行的值只跟上一行的值有關,即如果我們的目標是計算最終結果而不需要知道中間值的話,那麼剩下的那些都是無用數據。所以我們只需要存儲兩行,計算的時候讓其滾動即可。
下面附上一道whuoj的題,貼上代碼,註釋部分是樸素解。
#include <iostream>
using namespace std;
const int maxn = 102;
const int V=100005;
int c[maxn],v[maxn];
//int f[maxn][V];
int f1[V],f2[V];
int main(){
int n,maxv;
while(cin>>n){
for(int val=0;val<V;val++){
f1[val]=0;f2[val]=0;
}
for(int i=0;i<n;i++){
cin>>c[i]>>v[i];
if(i-c[i]>=0)
f1[i]=v[i];
else f1[i]=0;
}
cin>>maxv;
f2[0]=0;
for(int i=0;i<n;i++){
for(int val=1;val<=maxv;val++){
if(val-c[i]>=0&&(f1[val-c[i]]+v[i])>f1[val])
f2[val]=f1[val-c[i]]+v[i];
else f2[val]=f1[val];
}
for(int val=0;val<=maxv;val++)
f1[val]=f2[val];
}
cout<<f2[maxv]<<endl;
/*cin>>maxv;
for(int val=0;val<=maxv;val++)
if(val-c[0]>=0)
f[0][val]=v[0];
for(int i=1;i<n;i++)
for(int val=1;val<=maxv;val++)
if((val-c[i])>=0&&(f[i-1][val-c[i]]+v[i])>f[i-1][val])
f[i][val]=f[i-1][val-c[i]]+v[i];
else
f[i][val]=f[i-1][val];
for(int i=0;i<n;i++){
for(int v=0;v<=maxv;v++)
cout<<f[i][v]<<" ";
cout<<endl;
}
cout<<f[n-1][maxv]<<endl;*/
}
return 0;
}