揹包問題-01/完全/多重

揹包問題是典型的動態規劃的問題


對於0/1揹包,東西有兩種選擇,

第一,weight大於揹包剩餘體積,放棄之,

第二,weight小於揹包剩餘體積,再有兩種選擇,第一,不划算,放棄不要。。。第二,要,放進揹包


於是就有

weight[i]<=C  dp[i][j]=max(放進去,不放進去);


對於遞歸的寫法,不提倡,因爲如同Fib的寫法,效率很低,時間複雜度接近N^2

int knapSack(int C,int N, int weight[], int price[]){
    if(C==0 || N==0)
        return 0;
    if(weight[N]>C)
        return knapSack(C,N-1,weight,price);
    else
        return max(knapSack(C-weight[N],N-1,weight,price)+price[N],knapSack(C,N-1,weight,price));
}

對於遞歸沒有把以前算的值存起來導致的效率低下,我們可以考慮用二維數組來存貯,這樣就不要算N遍了。

改進版的應該是這樣的,但是用的還是遞歸的思想。

#include <iostream>
#include <queue>
#include <string>
#include <memory>
#include <algorithm>
#include <map>
using namespace std;;
int price[501],weight[501];
int dp[10][501];
int N,C;//N is the num of stuff, C is the capacity of bag
int main(void){
    freopen("input.txt","r",stdin);
    memset(price,0,sizeof(price));
    memset(weight,0,sizeof(weight));
    memset(dp,0,sizeof(dp));
    cin>>N>>C;
    for(int i=1;i<=N;i++)
        cin>>price[i]>>weight[i];

    for(int i=1;i<=N;i++){
        for(int j=1;j<=C;j++){
            dp[i][j] = dp[i-1][j];
            if(weight[i]<=j)
                dp[i][j]=max(dp[i-1][j-weight[i]]+price[i],dp[i-1][j]);
        }
    }
    cout<<dp[N][C]<<endl;
    return 0;
}

時間複雜度爲O(NC),空間複雜度即二維數組複雜度,O(NC),我們可以優化空間複雜度,即用一維數組代替二維。

但是0/1的一維數組代替是N正序,C倒序。。。。。

#include <iostream>
#include <queue>
#include <string>
#include <memory>
#include <algorithm>
#include <map>
using namespace std;;
int price[501],weight[501];
const int num_case = 1;
int dp[501];
int N,C;
int main(void){
    freopen("input.txt","r",stdin);
    //init data
    memset(price,0,sizeof(price));
    memset(weight,0,sizeof(weight));
    memset(dp,0,sizeof(dp));
    cin>>N>>C;
    for(int i=1;i<=N;i++)
        cin>>price[i]>>weight[i];

    for(int i=1;i<=N;i++){
        for(int j=C;j>=1;j--){
            if(weight[i]<=j)
                dp[j]=max(dp[j-weight[i]]+price[i],dp[j]);
        }
    }
    cout<<dp[C]<<endl;

    return 0;
}

如果N正序,C也正序的話,那是什麼了?---------> 完全揹包的一維數組解決方案


思想摘錄自博文:http://love-oriented.com/pack/P01.html

先考慮上面講的基本思路如何實現,肯定是有一個主循環i=1..N,每次算出來二維數組f[i][0..V]的所有值。那麼,如果只用一個數組f[0..V],能不能保證第i次循環結束後f[v]中表示的就是我們定義的狀態f[i][v]呢?f[i][v]是由f[i-1][v]和f[i-1][v-c[i]]兩個子問題遞推而來,能否保證在推f[i][v]時(也即在第i次主循環中推f[v]時)能夠得到f[i-1][v]和f[i-1][v-c[i]]的值呢?事實上,這要求在每次主循環中我們以v=V..0的順序推f[v],這樣才能保證推f[v]時f[v-c[i]]保存的是狀態f[i-1][v-c[i]]的值。僞代碼如下:

for i=1..N
    for v=V..0
        f[v]=max{f[v],f[v-c[i]]+w[i]};
其中的f[v]=max{f[v],f[v-c[i]]}一句恰就相當於我們的轉移方程f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]},因爲現在的f[v-c[i]]就相當於原來的f[i-1][v-c[i]]。如果將v的循環順序從上面的逆序改成順序的話,那麼則成了f[i][v]由f[i][v-c[i]]推知,與本題意不符,但它卻是另一個重要的揹包問題P02最簡捷的解決方案,故學習只用一維數組解01揹包問題是十分必要的。




完全揹包問題:

完全揹包,是N的數量不窮盡,不是拿完了就沒了,所以我們從比較好理解的二維數組入手,它應該是這樣的。


#include <iostream>
#include <queue>
#include <string>
#include <memory>
#include <algorithm>
#include <map>
using namespace std;;
int price[501],weight[501];
const int num_case = 1;
int dp[51][501];
int N,C;
int main(void){
    freopen("input.txt","r",stdin);
    //init data
    memset(price,0,sizeof(price));
    memset(weight,0,sizeof(weight));
    memset(dp,0,sizeof(dp));
    cin>>N>>C;
    for(int i=1;i<=N;i++)
        cin>>price[i]>>weight[i];

    for(int i=1;i<=N;i++){
        for(int j=1;j<=C;j++){
            dp[i][j] = dp[i-1][j];
            if(weight[i]<=j)
                dp[i][j]=max(dp[i][j-weight[i]]+price[i],dp[i-1][j]);
        }
    }
    cout<<dp[N][C]<<endl;

    return 0;
}

對於二維數組dp[i][j], 計算第N行的dp[i][] 都是將dp[i-1][] 先行拷貝下來,如果放進去的數值大於它,則更新。

如果是這樣的話,每行的DP操作,都不會涉及到上一行之前的數據,所以可以用一維數組來代替


#include <iostream>
#include <queue>
#include <string>
#include <memory>
#include <algorithm>
#include <map>
using namespace std;;
int price[501],weight[501];
const int num_case = 1;
int dp[501];
int N,C;
int main(void){
    freopen("input.txt","r",stdin);
    //init data
    memset(price,0,sizeof(price));
    memset(weight,0,sizeof(weight));
    memset(dp,0,sizeof(dp));
    cin>>N>>C;
    for(int i=1;i<=N;i++)
        cin>>price[i]>>weight[i];

    for(int i=1;i<=N;i++){
        for(int j=1;j<=C;j++){
            if(weight[i]<=j)
                dp[j]=max(dp[j-weight[i]]+price[i],dp[j]);
        }
    }
    cout<<dp[C]<<endl;

    return 0;
}

注意:在一維數組的0/1揹包中,C是倒序。一維數組的完全揹包中,C是正序,這是他們唯一的區別




多重揹包問題

說到多重揹包,就只得去看揹包九講的內容了。不再闡述。

但是下面的代碼,確實用一維數組做出來的最全面的集合體了。

zeroOnePack ==== 0/1

multiPack =======多重

completePack =====完全

它是將N的枚舉作為main中,因為他們幾個的N枚舉都是正序。

Weight的枚舉,0/1是逆序,complete是正序

多重是先試試complete,如果不行就用0/1暴力破解。

但是中間加入了logN的K=K*2這種處理做出了部分優化,下面的代碼希望能夠理解。

#include <iostream>
#include <queue>
#include <string>
#include <memory>
#include <algorithm>
#include <map>
using namespace std;
const int M=501;
int num[M],price[M],weight[M];
//num=object's number...price=object's price...weight=object's weight.
//weight should be less than C
//price stored in dp[], the more the better.
int dp[M];
int N,C;
extern int zeroOnePack(int p, int w);//p and w is int, not int[]
extern int multiPack(int p, int w, int n);
extern int completePack(int p, int w);
int main(void){
    freopen("input.txt","r",stdin);
    //init data
    memset(num,0,sizeof(num));
    memset(price,0,sizeof(price));
    memset(weight,0,sizeof(weight));
    memset(dp,0,sizeof(dp));
    cin>>N>>C;
    for(int i=1;i<=N;i++){
        cin>>num[i]>>price[i]>>weight[i];
        //cin>>price[i]>>weight[i];
    }
    for(int i=1;i<=N;i++){
        //zeroOnePack(price[i],weight[i]);
        //completePack(price[i],weight[i]);
        multiPack(price[i],weight[i],num[i]);
    }
    cout<<dp[C]<<endl;
    return 0;
}
int zeroOnePack(int p, int w){
    for(int j=C;j>=w;j--){
        dp[j]=max(dp[j-w]+p,dp[j]);
    }
    return 0;
}
int completePack(int p, int w){
    for(int j=w;j<=C;j++){
        dp[j]=max(dp[j-w]+p,dp[j]);
    }
    return 0;
}
int multiPack(int p, int w, int n){
    if(w*n>=C){
        completePack(p,w);
        return 0;
    }
    int k=1;
    while(k<n){
        zeroOnePack(p*k,w*k);
        n=n-k;
        k=2*k;
    }
    zeroOnePack(p*n,w*n);
    return 0;
}


寫在最後

如果感覺對這幾種揹包都熟悉了,就可以找以下鏈接去練習一下。

http://blog.csdn.net/liuqiyao_01/article/details/8477725

網路上還有個 ” 揹包九講“ 研究的很是深刻,可自行搜索。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章