1. 程式人生 > 其它 >動態規劃:揹包問題

動態規劃:揹包問題

01 揹包

揹包問題的理論基礎是01揹包。

有N件物品和一個最多能被重量為W 的揹包。第i件物品的重量是weight[i],得到的價值是value[i] 。每件物品只能用一次,求解將哪些物品裝入揹包裡物品價值總和最大。

例子:
揹包最大重量為4。

物品 重量 價值
物品0 1 15
物品1 3 20
物品2 4 30

問揹包能背的物品最大價值是多少?

動規五部曲

  1. 確定dp陣列以及下標的含義
    對於揹包問題是使用二維陣列,即dp[i][j] 表示從下標為[0-i]的物品裡任意取,放進容量為j的揹包,價值總和最大是多少。

  2. 確定遞推公式
    可以有兩個方向推出來dp[i][j],

    • 由dp[i - 1][j]推出,即揹包容量為j,裡面不放物品i的最大價值,此時dp[i][j]就是dp[i - 1][j]
    • 由dp[i - 1][j - weight[i]]推出,dp[i - 1][j - weight[i]] 為揹包容量為j - weight[i]的時候不放物品i的最大價值,那麼dp[i - 1][j - weight[i]] + value[i] (物品i的價值),就是揹包放物品i得到的最大價值
      所以遞迴公式:dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i])
  3. dp陣列如何初始化
    首先如果揹包容量j為0的話,即dp[i][0],無論是選取哪些物品,揹包價值總和一定為0。
    由狀態轉移方程可以看出i 是由 i-1 推匯出來,那麼i為0的時候就一定要初始化。dp[0][j]為存放編號0的物品的時候,各個容量的揹包所能存放的最大價值。

// 初始化需要倒敘遍歷,如果一旦正序遍歷了,那麼物品0就會被重複加入多次
for (int j = bagWeight; j >= weight[0]; j--) {
    dp[0][j] = dp[0][j - weight[0]] + value[0]; // 初始化i為0時候的情況
}
  1. 確定遍歷順序
    有兩個遍歷的維度:物品與揹包重量。兩者哪個先遍歷其實都可以,但是先遍歷物品更好理解。

先遍歷物品,然後遍歷揹包重量:

// weight陣列的大小 就是物品個數
for(int i = 1; i < weight.size(); i++) { // 遍歷物品
    for(int j = 0; j <= bagWeight; j++) { // 遍歷揹包容量 
        if (j < weight[i]) dp[i][j] = dp[i - 1][j]; // 這個是為了展現dp數組裡元素的變化
        else dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
        
    }
}

先遍歷揹包,再遍歷物品:

// weight陣列的大小 就是物品個數
for(int j = 0; j <= bagWeight; j++) { // 遍歷揹包容量
    for(int i = 1; i < weight.size(); i++) { // 遍歷物品
        if (j < weight[i]) dp[i][j] = dp[i - 1][j];
        else dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
    }
}
  1. 舉例推導dp陣列
    來看一下對應的dp陣列的數值,如圖:

程式碼

    public static void main(String[] args){
        int[] weight = {1, 3, 4};
        int[] value = {15, 20, 30};
        int bagWeight = 4;

        // 二維陣列
        int[][] dp = new int[weight.length][bagWeight + 1];

        // 初始化
        for (int j = bagWeight; j >= weight[0]; j--) {
            dp[0][j] = dp[0][j - weight[0]] + value[0];
        }

        // weight陣列的大小 就是物品個數
        for(int i = 1; i < weight.length; i++) { // 遍歷物品
            for(int j = 0; j <= bagWeight; j++) { // 遍歷揹包容量
                if (j < weight[i]) dp[i][j] = dp[i - 1][j];
                else dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);

            }
        }

        System.out.println(dp[weight.length - 1][bagWeight]);
    }