揹包問題:0-1揹包、徹底揹包和多重揹包

揹包問題泛指如下這一種問題:ios

給定一組有固訂價值和固定重量的物品,以及一個已知最大承重量的揹包,求在不超過揹包最大承重量的前提下,能放進揹包裏面的物品的最大總價值。數組

這一類問題是典型的使用動態規劃解決的問題,咱們能夠把揹包問題分紅3種不一樣的子問題:0-1揹包問題、徹底揹包和多重揹包問題。下面對這三種問題分別進行討論。spa

 

1.0-1揹包問題.net

0-1揹包問題是指每一種物品都只有一件,能夠選擇放或者不放。如今假設有n件物品,揹包承重爲m。code

對於這種問題,咱們能夠採用一個二維數組去解決:f[i][j],其中i表明加入揹包的是前i件物品,j表示揹包的承重,f[i][j]表示當前狀態下能放進揹包裏面的物品的最大總價值。那麼,f[n][m]就是咱們的最終結果了。blog

採用動態規劃,必需要知道初始狀態和狀態轉移方程。初始狀態很容易就能知道,那麼狀態轉移方程如何求呢?對於一件物品,咱們有放進或者不放進揹包兩種選擇:ip

  (1)假如咱們放進揹包,f[i][j] = f[i - 1][j - weight[i]] + value[i],這裏的f[i - 1][j - weight[i]] + value[i]應該這麼理解:在沒放這件物品以前的狀態值加上要放進去這件物品的價值。而對於f[i - 1][j - weight[i]]這部分,i - 1很容易理解,關鍵是 j - weight[i]這裏,咱們要明白:要把這件物品放進揹包,就得在揹包裏面預留這一部分空間。ci

  (2)假如咱們不放進揹包,f[i][j] = f[i - 1][j],這個很容易理解。leetcode

    所以,咱們的狀態轉移方程就是:f[i][j] = max(f[i][j] = f[i - 1][j] , f[i - 1][j - weight[i]] + value[i])  博客

    固然,還有一種特殊的狀況,就是揹包放不下當前這一件物品,這種狀況下f[i][j] = f[i - 1][j]。

下面是實現的代碼:

#include <iostream>
#define V 500
using namespace std;
int weight[20 + 1];
int value[20 + 1];
int f[20 + 1][V + 1];
int main() {
    int n, m;
    cout << "請輸入物品個數:";
    cin >> n;
    cout << "請分別輸入" << n << "個物品的重量和價值:" << endl; 
    for (int i = 1; i <= n; i++) {
        cin >> weight[i] >> value[i];
    }
    cout << "請輸入揹包容量:";
    cin >> m;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            if (weight[i] > j) {
                f[i][j] = f[i - 1][j];
            }
            else {
                f[i][j] = f[i - 1][j] > f[i - 1][j - weight[i]] + value[i] ? f[i - 1][j] : f[i - 1][j - weight[i]] + value[i];
            }
        }
    }    
    cout << "揹包能放的最大價值爲:" << f[n][m] << endl;
}

 

特別的是,0-1揹包問題還有一種更加節省空間的方法,那就是採用一維數組去解決,下面是代碼:

#include <iostream>
#define V 500
using namespace std;
int weight[20 + 1];
int value[20 + 1];
int f[V + 1];
int main() {
    int n, m;
    cout << "請輸入物品個數:";
    cin >> n;
    cout << "請分別輸入" << n << "個物品的重量和價值:" << endl; 
    for (int i = 1; i <= n; i++) {
        cin >> weight[i] >> value[i];
    }
    cout << "請輸入揹包容量:";
    cin >> m;
    for (int i = 1; i <= n; i++) {
        for (int j = m; j >= 1; j--) {
            if (weight[i] <= j) {
                f[j] = f[j] > f[j - weight[i]] + value[i] ? f[j] : f[j - weight[i]] + value[i];
            }
        }
    }
    cout << "揹包能放的最大價值爲:" << f[m] << endl;
}

 

我看過不少博客的描述,講得都不太清楚:爲何要把第二層循環顛倒過來呢?我認爲要理解這種方法,用圖是最合適不過了,我在另一個博客(http://blog.csdn.net/mu399/article/details/7722810)找到了這樣一個圖:

這個表格對於理解0-1揹包問題頗有用,咱們利用它來理解一下爲何要把第二層循環顛倒這個問題。考慮d9這一項,要求出這個狀態,咱們有可能利用到的就是e1到e8這8個狀態,當咱們把第二層循環顛倒過來時,當咱們要求f[j]時,f[j -1]到f[1]還保存着下面一行的狀態,所以能夠採用一維數組解決。咱們能夠考慮一下假如不把第二層循環顛倒,當要求f[j]時,f[j - 1]到f[1]已是同一行的狀態了,根本無法求。

我在LeetCode上也曾作過相似的題目——120 Triangle(https://leetcode.com/problems/triangle/description/),也是把二維數組簡化爲一維數組去解決問題。

 

更新:

for (int i = 1; i <= n; i++) {
     for (int j = m; j >= 1; j--) {
         if (weight[i] <= j) {
             f[j] = f[j] > f[j - weight[i]] + value[i] ? f[j] : f[j - weight[i]] + value[i];
         }
     }
}

這是0-1揹包最重要的部分,能夠把它改成下面的更簡潔的版本:

for (int i = 1; i <= n; i++) {
    for (int j = m; j >= weight[i]; j--) {
         f[j] = f[j] > f[j - weight[i]] + value[i] ? f[j] : f[j - weight[i]] + value[i];
    }
}

 

2.徹底揹包問題

徹底揹包問題是指每種物品都有無限件,具體的解法我也解釋不清楚,只能先放代碼,談談個人理解了:

#include <iostream>
#define V 500
using namespace std;
int weight[20 + 1];
int value[20 + 1];
int f[V + 1];
int max(int a, int b) {
    return a > b ? a : b;
}
int main() {
    int n, m;
    cout << "請輸入物品個數:";
    cin >> n;
    cout << "請分別輸入" << n << "個物品的重量和價值:" << endl; 
    for (int i = 1; i <= n; i++) {
        cin >> weight[i] >> value[i];
    }
    cout << "請輸入揹包容量:";
    cin >> m;
    for (int i = 1; i <= n; i++) {
        for (int j = weight[i]; j <= m; j++) {
            f[j] = max(f[j], f[j - weight[i]] + value[i]);
        }
    }
    cout << "揹包能放的最大價值爲:" << f[m] << endl;
}

 

i=1時,計算只放第一件物品的最大價值。

i=2時,計算加上第二件物品的最大價值(在只放第一件物品的前提下)

以此類推……

值得注意的是,第二層循環要從j=weight[i]開始,這個稍微理解一下便可。

 

我在別的博客看到了一段分析0-1揹包問題和徹底揹包問題區別的話,以爲對理解這兩個問題頗有幫助,所以截圖以下:

 

 

3.多重揹包問題

多重揹包問題限定了一種物品的個數,解決多重揹包問題,只須要把它轉化爲0-1揹包問題便可。好比,有2件價值爲5,重量爲2的同一物品,咱們就能夠分爲物品a和物品b,a和b的價值都爲5,重量都爲2,但咱們把它們視做不一樣的物品。

代碼以下:

#include <iostream>
using namespace std;
#define V 1000
int weight[50 + 1];
int value[50 + 1];
int num[20 + 1];
int f[V + 1];
int max(int a, int b) {
    return a > b ? a : b;
}
int main() {
    int n, m;
    cout << "請輸入物品個數:";
    cin >> n;
    cout << "請分別輸入" << n << "個物品的重量、價值和數量:" << endl; 
    for (int i = 1; i <= n; i++) {
        cin >> weight[i] >> value[i] >> num[i];
    }
    int k = n + 1;
    for (int i = 1; i <= n; i++) {
        while (num[i] != 1) {
            weight[k] = weight[i];
            value[k] = value[i];
            k++;
            num[i]--;
        }
    }
    cout << "請輸入揹包容量:";
    cin >> m;
    for (int i = 1; i <= k; i++) {
        for (int j = m; j >= 1; j--) {
            if (weight[i] <= j) f[j] = max(f[j], f[j - weight[i]] + value[i]);
        }
    }
    cout << "揹包能放的最大價值爲:" << f[m] << endl;
}
相關文章
相關標籤/搜索