背包問題

動態規劃 Dynamic Programming

林芊妘

背包問題是組合優化問題中一個經典的範例,目的是: 在容量有限的背包裡,選擇價值最大化的物品組合

背包是一種比喻

在日常生活中我們常常面臨這樣的選擇

  • 書包容量有限,段考前要帶什麼回家抱佛腳
  • 錢包預算有限,要買什麼最能填飽肚子
  • 考前自習時間有限,要讀什麼才能保住學分

容量W

重量W1

價值V1

🥘

重量W2

價值V2

👔

重量W3

價值V3

目標:總重量<=w;總價值最大

0-1背包問題

容量W

重量W1

價值V1

🥘

重量W2

價值V2

👔

重量W3

價值V3

物品只能取一次或不取

重量:1

價值:3

重量:4

價值:4

重量:3

價值:7

容量:5

0 1 2 3 4 5
0
(1,3)
🥘(4,4)
👔(3,7)
0 1 2 3 4 5
0 0 0 0 0 0 0
(1,3)
🥘(4,4)
👔(3,7)

任意背包容量中,不放入任何物品,則最大價值皆為0

0 1 2 3 4 5
0 0 0 0 0 0 0
(1,3) 0
🥘(4,4) 0
👔(3,7) 0

背包容量為0,因此無法裝入任何物品,則最大價值皆為0

0 1 2 3 4 5
0 0 0 0 0 0 0
(1,3) 0 3
🥘(4,4) 0
👔(3,7) 0
0 1 2 3 4 5
0 0 0 0 0 0 0
(1,3) 0 3 3 3 3 3
🥘(4,4) 0
👔(3,7) 0

*先以行確認

0 1 2 3 4 5
0 0 0 0 0 0 0
(1,3) 0 3 3 3 3 3
🥘(4,4) 0 3
👔(3,7) 0
0 1 2 3 4 5
0 0 0 0 0 0 0
(1,3) 0 3 3 3 3 3
🥘(4,4) 0 3 3 3
👔(3,7) 0
0 1 2 3 4 5
0 0 0 0 0 0 0
(1,3) 0 3 3 3 3 3
🥘(4,4) 0 3 3 3 4
👔(3,7) 0
0 1 2 3 4 5
0 0 0 0 0 0 0
(1,3) 0 3 3 3 3 3
🥘(4,4) 0 3 3 3 4 7
👔(3,7) 0
0 1 2 3 4 5
0 0 0 0 0 0 0
(1,3) 0 3 3 3 3 3
🥘(4,4) 0 3 3 3 4 7
👔(3,7) 0 3
0 1 2 3 4 5
0 0 0 0 0 0 0
(1,3) 0 3 3 3 3 3
🥘(4,4) 0 3 3 3 4 7
👔(3,7) 0 3 3
0 1 2 3 4 5
0 0 0 0 0 0 0
(1,3) 0 3 3 3 3 3
🥘(4,4) 0 3 3 3 4 7
👔(3,7) 0 3 3 7
0 1 2 3 4 5
0 0 0 0 0 0 0
(1,3) 0 3 3 3 3 3
🥘(4,4) 0 3 3 3 4 7
👔(3,7) 0 3 3 7 10
0 1 2 3 4 5
0 0 0 0 0 0 0
(1,3) 0 3 3 3 3 3
🥘(4,4) 0 3 3 3 4 7
👔(3,7) 0 3 3 7 10 10

每格中的值即為當下情況的最優解,因此右下角的值即為答案

以程式的角度思考

0 1 2 3 4 5
0 0 0 0 0 0 0
(1,3) 0 3 3 3 3 3
🥘(4,4) 0 3 3 3 4 7
👔(3,7) 0 3 3 7 10 10

不納入棒球的最優解

0

以程式的角度思考

0 1 2 3 4 5
0 0 0 0 0 0 0
(1,3) 0 3 3 3 3 3
🥘(4,4) 0 3 3 3 4 7
👔(3,7) 0 3 3 7 10 10

0

0+3

背包容量減去棒球重量時的最優解

0 1 2 3 4 5
0 0 0 0 0 0 0
(1,3) 0 3 3 3 3 3
🥘(4,4) 0 3 3 3 4 7
👔(3,7) 0 3 3 7 10 10

以程式的角度思考

不納入火鍋料的最優解

3

0 1 2 3 4 5
0 0 0 0 0 0 0
(1,3) 0 3 3 3 3 3
🥘(4,4) 0 3 3 3 4 7
👔(3,7) 0 3 3 7 10 10

以程式的角度思考

3

背包容量減去火鍋料重量時的最優解

3+4

0 1 2 3 4 5
0 0 0 0 0 0 0
(1,3) 0 3 3 3 3 3
🥘(4,4) 0 3 3 3 4 7
👔(3,7) 0 3 3 7 10 10

以程式的角度思考

4

不納入襯衫的最優解

0 1 2 3 4 5
0 0 0 0 0 0 0
(1,3) 0 3 3 3 3 3
🥘(4,4) 0 3 3 3 4 7
👔(3,7) 0 3 3 7 10 10

以程式的角度思考

4

背包容量減去襯衫重量時的最優解

3+7

#include <bits/stdc++.h>
using namespace std;
int main(){
    //m為物品數,n為背包容量
    int m,n;
    while(cin>>m>>n){
        //各物品重量與價值
        int w[m],v[m];
        //dp為表格
        int dp[m+1][n+1];
        for(int i=0;i<m;i++)
            cin>>w[i]>>v[i];
        memset(dp,0,sizeof(dp));//初始化把表格清空
        //dp[i][j]為表格中最優解
        for(int i=0;i<m;i++){
            for(int j=0;j<=n;j++){
                //物品重量小於背包容量
                if(w[i]<=j){
                    dp[i+1][j]=max(dp[i][j],
                    dp[i][j-w[i]]+v[i]);
                }
                //容量不足繼承上方的
                else dp[i+1][j]=dp[i][j];
            }
        }
        cout<<dp[m][n]<<endl;
    }
}

二維版本

二維–>一維

二維 一維
可讀性 容易 抽象
程式長度 較長 精簡
儲存空間
更新順序 皆可 倒序

考慮同一層i

dp[5] = max(dp[5], dp[5-w1] + v1)
dp[4] = dp[4-w1] + v1   
dp[3] = dp[3-w1] + v1  
dp[2] = dp[2-w1] + v1  
dp[1] = dp[1-w1] + v1

可改成一維!

物品1

物品2

dp[5] = max(dp[5], dp[5-w2] + v2)
...
dp[w2] = max(dp[w2], dp[0] + v2)
#include<bits/stdc++.h>
using namespace std;
int main(){
    int m,n;
    while(cin>>m>>n){
        int w[m],v[m];
        for(int i=0;i<m;i++){
            cin>>w[i]>>v[i];
        }
        int dp[n+1]={0}; 
        //遍歷每一個物品
        for(int i=0;i<m;i++){
            //這裡從n到w[i]倒序遍歷
            for(int j=n;j>=w[i];j--){
                //dp[j]表示容量為j時的最優解
                dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
            }
        }
        cout<<dp[n]<<endl;//最終背包容量為n時的最大價值
    }
}

一維版本

你是個無課玩家,每天只有10點體力值,以下有5隻哥布林供你攻略,由於你前幾天抽中精靈,所以每次攻略都能成功,你想在今天獲得最多經驗值該攻略哪幾隻哥布林呢?

練習題-哥布林攻略(畫表格)

消耗體力 經驗值
勤儉持家哥布林 2 6
肥宅哥布林 3 2
180哥布林 4 5
狗狗哥布林 4 7
嬰兒哥布林 6 4

下週上課交來給我可以領餅乾!!

完全背包問題

容量W

重量W1

價值V1

🥘

重量W2

價值V2

👔

重量W3

價值V3

物品可以無限取

重量:1

價值:3

重量:4

價值:4

重量:3

價值:7

容量:5

#include <bits/stdc++.h>
using namespace std;
int main(){
    //m為物品數,n為背包容量
    int m, n;
    cin>>m>>n;
    //物品的重量與價值
    int w[m], v[m];
    for (int i=0;i<m;i++){
        cin >> w[i] >> v[i];
    }
    int dp[n+1]={0}; 

    //每個物品都試著放進背包
    for (int i=0;i<m;i++) {
        // 正序:因為可以重複放
        for (int j=w[i];j<=n;j++) {
            //dp[j]表示容量為j時的最優解
            dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
        }
    }
    //輸出最優解
    cout<<dp[n]<<endl; 
    return 0;
}

背包問題

By chainy

背包問題

  • 130