0-1揹包問題(動態規劃)

QQ896357473發表於2020-10-31

#include <iostream>
#include <cstring>
using namespace std;
 
 
const int N=15;
 
 
int main()
{
    int v[N]={0,8,10,6,3,7,2};
    int w[N]={0,4,6,2,2,5,1};
 
 
    int m[N][N];
    int n=6,c=12;
    memset(m,0,sizeof(m));
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=c;j++)
        {
            if(j>=w[i])
                m[i][j]=max(m[i-1][j],m[i-1][j-w[i]]+v[i]);
 
 
            else
                m[i][j]=m[i-1][j];
        }
    }
 
 
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=c;j++)
        {
            cout<<m[i][j]<<' ';
        }
        cout<<endl;
    }
 
 
    return 0;
}

問題描述:0-1揹包問題,給定n種物品,和一個容量為C的揹包,物品 i  的重量是W[ i ] ,價值為V[ i ] ,問:應該如何選擇裝入揹包的物品,使得裝入揹包中物品的價值最大?

要求,對於每一個物品,我們只能選擇拿與不拿,並不能裝入物品的一部分,也不能裝入多個同樣的物品。

解題思路:在給定空間C的情況下選擇 n 個物品中的某些物品,對於每一個物品只有拿與不拿。可以想象在選擇第i個物品時可以拿,也可以不拿,如果拿了第i個物品,這時候的價值可以等價於   用C-w[i]的空間選擇i-1個物品的價值 + v[ i ] 。 如果不拿,這時候的價值相當於利用C的空間選擇i-1個物品的價值(注意,不拿的情況下空間大,最優解不一定小於拿的情況。)。因此,對於第i個物品的選擇由前邊的兩種情況的最大值決定。(這中解題思路是一種逆向思維,是從後邊往前走的。而在計算時是由前往後計算的)

建立模型:宣告一個大小為m[ n ][ c ]的二維陣列,m[i][j]表示,在面對第i件物品的選擇時,利用j的空間選擇前i件物品能得到的最大值。根據上邊的分析很容易得到m[i][j]的計算方法。

(1)如果j < w [ i ] ,這時候揹包容量不足以放下第 i 件物品,只能選擇不拿,這時候對於m[i][j]的選擇只能有 m[ i - 1 ][ j ](在j空間中對前i-1個物品進行最優選擇)所決定。

          m[ i ][ j ] = m[ i-1 ][ j ]

(2)如果j<w[i],這時候揹包容量可以放下第i件物品,我們就要考慮放下這個物品能否創造最大價值,即第i件物品是否在最優解中,因此這時候我們就要比較

           m[ i - 1 ][ j ]  和  m[ i-1 ][ c - w[ i ] ] (在c - w[ i ] 的空間中面對i - 1 個物品進行最優選擇,這裡的m[ i-1 ][ j-w[ i ] ]指的就是考慮了i-1件物品,揹包容量為j-w[i]時的最大價             值,也是相當於為第i件物品騰出了w[i]的空間。大小來判斷 i 是否選擇,(而在實際計算中我們對於這兩項並不瞭解,因此需要從前往後計算)。

狀態轉移方程:

if(j>=w[i])
    m[i][j]=max(m[i-1][j],m[i-1][j-w[i]]+v[i]);
else
    m[i][j]=m[i-1][j];

例:0-1揹包問題:(來源:https://blog.csdn.net/xp731574722/article/details/70766804)

        在使用動態規劃演算法求解0-1揹包問題時,使用二維陣列m[ i ][ j ]儲存     j 空間下面對 i 件物品的最優解  ,而使用動態規劃演算法求解時其實就是對二維陣列m[ i ][ j ]的繪製過程。

        價值陣列  V[ n ] = {8, 10, 6, 3, 7, 2}

        重量陣列  M[ n ] = {4, 6, 2, 2, 5, 1}            揹包容量為C = 12

        

(第一行和第一列為序號,其數值為0)
如m[2][6],在面對第二件物品,揹包容量為6時我們可以選擇不拿,那麼獲得價值僅為第一件物品的價值8,如果拿,就要把第一件物品拿出來,放第二件物品,價值10,那我們當然是選擇拿。m[2][6]=m[1][0]+10=0+10=10;依次類推,得到m[6][12]就是考慮所有物品,揹包容量為C時的最大價值。

程式碼實現:


#include <iostream>
#include <cstring>
using namespace std;
 
 
const int N=15;
 
 
int main()
{
    int v[N]={0,8,10,6,3,7,2};
    int w[N]={0,4,6,2,2,5,1};
 
 
    int m[N][N];
    int n=6,c=12;
    memset(m,0,sizeof(m));
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=c;j++)
        {
            if(j>=w[i])
                m[i][j]=max(m[i-1][j],m[i-1][j-w[i]]+v[i]);
 
 
            else
                m[i][j]=m[i-1][j];
        }
    }
 
 
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=c;j++)
        {
            cout<<m[i][j]<<' ';
        }
        cout<<endl;
    }
 
 
    return 0;
}

到這一步,可以確定的是可能獲得的最大價值,但是我們並不清楚具體選擇哪幾樣物品能獲得最大價值。

另起一個 x[ ] 陣列,x[i]=0表示不拿,x[i]=1表示拿。

m[n][c]為最優值,如果m[n][c]=m[n-1][c] ,說明有沒有第n件物品都一樣,則x[n]=0 ; 否則 x[n]=1。當x[n]=0時,由x[n-1][c]繼續構造最優解;當x[n]=1時,則由x[n-1][c-w[i]]繼續構造最優解。以此類推,可構造出所有的最優解。


void traceback()
{
    for(int i=n;i>1;i--)
    {
        if(m[i][c]==m[i-1][c])
            x[i]=0;
        else
        {
            x[i]=1;
            c-=w[i];
        }
    }
    x[1]=(m[1][c]>0)?1:0;
}

參考(https://blog.csdn.net/xp731574722/article/details/70766804

相關文章