1. 程式人生 > >0——1揹包問題動態規劃求解

0——1揹包問題動態規劃求解

轉自https://www.cnblogs.com/wujing-hubei/p/6376218.html?utm_source=tuicool&utm_medium=referral

這篇文章把動態規劃過程剖析的很好

基本思想:

動態規劃演算法通常用於求解具有某種最優性質的問題。在這類問題中,可能會有許多可行解。每一個解都對應於一個值,我們希望找到具有最優值的解。動態規劃演算法與分治法類似,其基本思想也是將待求解問題分解成若干個子問題,先求解子問題,然後從這些子問題的解得到原問題的解。與分治法不同的是,適合於用動態規劃求解的問題,經分解得到子問題往往不是互相獨立的(即下一個子階段的求解是建立在上一個子階段的解的基礎上,進行進一步的求解)。

若用分治法來解這類問題,則分解得到的子問題數目太多,有些子問題被重複計算了很多次。如果我們能夠儲存已解決的子問題的答案,而在需要時再找出已求得的答案,這樣就可以避免大量的重複計算,節省時間。我們可以用一個表來記錄所有已解的子問題的答案。不管該子問題以後是否被用到,只要它被計算過,就將其結果填入表中。這就是動態規劃法的基本思路。具體的動態規劃演算法多種多樣,但它們具有相同的填表格式

應用場景:

適用動態規劃的問題必須滿足最優化原理、無後效性和重疊性。
1、最優化原理(最優子結構性質) 最優化原理可這樣闡述:一個最優化策略具有這樣的性質,不論過去狀態和決策如何,對前面的決策所形成的狀態而言,餘下的諸決策必須構成最優策略。簡而言之,一個最優化策略的子策略總是最優的。一個問題滿足最優化原理又稱其具有最優子結構性質。

2、無後效性  將各階段按照一定的次序排列好之後,對於某個給定的階段狀態,它以前各階段的狀態無法直接影響它未來的決策,而只能通過當前的這個狀態。換句話說,每個狀態都是過去歷史的一個完整總結。這就是無後向性,又稱為無後效性。

3、子問題的重疊性  動態規劃將原來具有指數級時間複雜度的搜尋演算法改進成了具有多項式時間複雜度的演算法。其中的關鍵在於解決冗餘,這是動態規劃演算法的根本目的。動態規劃實質上是一種以空間換時間的技術,它在實現的過程中,不得不儲存產生過程中的各種狀態,所以它的空間複雜度要大於其它的演算法。

下面是一個關於 0-1揹包問題 的動態規劃思想PPT截圖:

問題描述:
  給定n種物品和一揹包。物品i的重量是wi,其價值為vi,揹包的容量為C。問應如何選擇裝入揹包的物品,使得裝入揹包中物品的總價值最大?

對於一種物品,要麼裝入揹包,要麼不裝。所以對於一種物品的裝入狀態可以取0和1.我們設物品i的裝入狀態為xi,xi∈ (0,1),此問題稱為0-1揹包問題。

  資料:物品個數n=5,物品重量w[n]={0,2,2,6,5,4},物品價值V[n]={0,6,3,5,4,6},
(第0位,置為0,不參與計算,只是便於與後面的下標進行統一,無特別用處,也可不這麼處理。)總重量c=10。揹包的最大容量為10,那麼在設定陣列m大小時,可以設行列值為6和11,那麼,對於m(i,j)就表示可選物品為i…n,揹包容量為j(總重量)時揹包中所放物品的最大價值。

最優值分析過程如下:

  當揹包為空時,首先分析將物品n放入揹包,即在總重量分別為0到10時,如何放置物品n使總價值最大。

  對於m[5][j],當j<w[5]時,物品5不能放入揹包中,此時揹包的價值為0。當j>=w[5]時,物品5可以放入揹包,此時揹包的價值為v[5]。得到結果如下表:

  在物品5的基礎上分析物品4,

  當j<w[4]時,物品4不能放入,此時揹包的最大價值為m[4+1][j];即m[4][0..4]=m[5][0..4]

  當j>=w[4]時,物品4要麼放入要麼不放入。當物品4放入揹包後,對於物品4+1到n,能達到的最大價值為m[4+1][j-w[4]]+v[4],故此時能達到的最大價值為m[4+1][j-w[4]]+v[4]

  當物品4不放入揹包時,能達到的最大價值為m[4+1][j]。最後比較放入與不放入情況下,兩者的最大值取其大者,分析結果如下:

  由前面分析過程得m[i][j]的遞迴過程如下:

                                                       

  最終得到如下結果:

                                

構造最優解

  最優解的構造可根據C列的資料來構造最優解,構造時從第一個物品開始。從i=1,j=c即m[1][c]開始。  

  1、對於m[i][j],如果m[i][j]==m[i+1][j],則物品i沒有裝入揹包,否則物品i裝入揹包;

    2、為了確定後繼即物品i+1,應該尋找新的j值作為參照。如果物品i已放入揹包,則j=j-w[i];如果物品i未放入揹包,則j=j。

  3、重複上述兩步判斷後續物品i到物品n-1是否放入揹包。

  4、對於物品n,直接通過m[n][j]是否為0來判斷物品n是否放入揹包。

複製程式碼
#include<iostream>
#include<stack>
#include<vector>

using namespace std;

stack<int> KnapSack(int c,vector<int> w,vector<int> v,int &max_m)
{
    vector<vector <int> > m(w.size(),vector<int>(c+1));
    stack<int> res;
    int i,j;
    
    max_m=0;
    for(j=0;j<c+1;j++) //對於m[n][j],
        if(j<w[w.size()-1])
            m[w.size()-1][j]=0;  //當j<w[n]時,物品n不能放入揹包中,此時揹包的價值為0。
        else 
            m[w.size()-1][j]=v[v.size()-1];  //當j>=w[n]時,物品n可以放入揹包,此時揹包的價值為v[n]
    
    for(i=w.size()-2;i>=0;i--) //對於m[i][j],
    {
        for(j=0;j<c+1;j++) 
            if(j<w[i])          //當j<w[i]時,物品i不能放入揹包中,此時揹包的價值為m[i+1][j]。 
                m[i][j]=m[i+1][j];
            else                //當j>=w[i]時,物品n可以放入揹包
            {
                int m1=m[i+1][j];  //當物品i不放入揹包時,能達到的最大價值為m[i+1][j]
                int m2=m[i+1][j-w[i]]+v[i];  //當物品i放入揹包後,對於物品i+1到n,能達到的最大價值為m[i+1][j-w[i]]+v[i]
                m[i][j]=m1>m2?m1:m2;         //兩者取其大者        
            }    
    } 
/*    cout << "最優值矩陣:"<<endl;
    for(i=0;i<w.size();i++)
    {
        for(j=0;j<c+1;j++)
            cout<<m[i][j]<<" ";
        cout <<endl;
    }    
    cout <<endl;
    */
    j=c;
    for(i=0;i<w.size()-1;i++)
    {
        if(m[i][j]!=m[i+1][j])
        {
            res.push(i+1);
            max_m+=v[i];
            j=j-w[i];        
        }
    }    
    if(m[w.size()-1][j]!=0)
    {
        res.push(w.size());
        max_m+=v[w.size()-1];
    }    
        
    return res;    
}

int main()
{
    vector<int> weight;
    vector<int> value;
    stack<int>  result;
    int max_weight; 
    int tmp; 
    int result_m=0;
    
    cout<< "輸入揹包最大容量"<<endl;
    cin >> max_weight; 
    cout <<"輸入物品重量,以0結束"<<endl;
    
    while(1)
    {
        cin>>tmp;
        if(tmp!=0)
            weight.push_back(tmp);
        else 
            break;
    }    
    cout <<"物品重量: "<<endl;
    for(int i=0;i<weight.size();i++)
        cout <<weight[i]<<" ";
    cout << endl;
    cout <<"輸入物品權重,以0結束"<<endl;
    while(1)
    {
        cin>>tmp;
        if(tmp!=0)
            value.push_back(tmp);
        else
            break;
    }
        
    cout <<"物品權重: "<<endl;
    for(int i=0;i<value.size();i++)
        cout <<value[i]<<" ";
    cout << endl;
    
    result=KnapSack(max_weight,weight,value,result_m);
    
    cout <<"放入揹包的物品為:"<<endl; 
    while(!result.empty())
    {
        cout <<result.top()<<" ";
        result.pop();
    }
    cout <<endl;
    cout<<"揹包最大價值為:"<<result_m<<endl;
    
    return 0;
}
複製程式碼