0-1揹包問題


  • 問題描述:

    有 n 件物品和一個最大承重爲 W 的揹包,每件物品的重量是 𝑤i、價值是 𝑣ijava

    在保證總重量不超過 W 的前提下,選擇某些物品裝入揹包,揹包的最大總價值是多少?數組

    注意:每一個物品只有 1 件,也就是每一個物品只能選擇 0 件或者 1 件優化

  • 問題分析:

    這是一個典型的動態規劃問題:code

    ​ 1.設置一個values的物品價值數組和一個相對應的物品重量數組weigths.blog

    ​ 2.物品的編號爲i,因爲後續使用數組下標的緣由,i從1開始,因此:價值是values[i-1],重量爲weights[i-1]。ci

    3.狀態方程:it

    dp[i][j] = 表示有前i個物品能夠選擇,且揹包容量爲j的狀況下能夠獲取的最大價值

    4.當 j < weights[ i -1 ]時:io

    dp[i][j] = dp[i-1][j];

    5.當 j > = weights[i-1] 時:class

    dp [i][j] = Math.max (dp[i – 1, j], dp[i – 1, j – weights[i – 1]] + values[i – 1] )
    values[i – 1] //表示當前物品的價值  
    dp[i – 1, j – weights[i – 1]] // 表示取了當前物品後剩餘空間的最大價值
    //綜合:當j > = weights[i-1] 時,當前的物品能夠選擇取或者不取
        (1)不取的話,很明顯,最大價值就是上一步的最大價值dp[i][j] = dp[i-1][j]
        (2)取的話,價值就爲dp[i – 1, j – weights[i – 1]] + values[i – 1]
    //因爲不知道取仍是不取時得到的價值最高,因此對兩個數取最大值。
  • 代碼實現:

    class Solution{
        public void knapsackProblem(){
            int[] weight = {2,2,6,5,4};
            int[] values = {6,3,5,4,6};
            int capacity = 10;
            int n = values.length;
            //由於要空出第一行和第一列 因此dp數組要加一
            int[][] dp = new int[values.length+1][capacity+1];
    
            for (int i = 1; i < dp.length; i++) {
                for (int j = 1; j < dp[0].length; j++) {
                    if (j < weight[i-1]){
                        dp[i][j] = dp[i-1][j];
                    }else {
                        dp[i][j] = Math.max(dp[i-1][j],values[i-1]+dp[i-1][j-weight[i-1]]);
                    }
                }
            }
          //輸出二維數組
            for (int i = 0; i < dp.length; i++) {
                for (int j = 0; j < dp[0].length; j++) {
                    System.out.print(dp[i][j] + "  ");
                }
                System.out.println();
            }
          //最終結果
            System.out.println(dp[values.length][capacity]);
        }
    }
  • 方案優化

    ​ 好比當咱們求dp[4][7]的時候,只有兩種選擇,要麼去4號物品,要麼不取。若是不取的話,那這一決策的最大價值就是上一步決策獲得的最大價值dp[3][7],若是取的話,就是當前物品的價值加上剩餘空間的最大價值dp[ 3 ][ 2 ] = 6, 6+4 = 10.和圖中結果同樣。遍歷

    由上一個方案的執行過程咱們能夠發現這樣的規律:

    ​ 當咱們求dp[i][j]時,只會用到其當前物品的價值和上一步決策的最大值(2號區域)以及前面的某一個數值(3號區域),那麼咱們就能夠把它簡化成一維數組,從後往前求取,這樣可使得代碼更加簡單。

  • 優化代碼實現:

    package DynamicProgramming;
    
    public class KnapsackBest {
        public static void main(String[] args){
            int[] w = {1,3,5,4,3};
            int[] v = {5,7,10,8,6};
            int c = 10;
            System.out.println(maxValue(w,v,c));
    
        }
    
        public static int maxValue(int[] w,int[] v,int c){
            //判斷數組是否符合要求
            if (w.length == 0 || w == null) return 0;
            if (v.length == 0 || v == null) return 0;
            if (w.length != v.length || c <= 0) return 0;
          //用於存儲決策價值的數組
            int[] dp = new int[c+1];
            for (int i = 1; i < v.length+1 ; i++) {
                //從後往前遍歷,當j < w[i-1]的狀況下,直接使用上一步的數組結果
                for (int j = c; j >= w[i-1] ; j--) {
                    //從後往前,覆蓋上一物品時產生的結果
                    dp[j] = Math.max(dp[j],v[i-1] + dp[j-w[i-1]]);
                }
            }
    
            return dp[c];
        }
    }

  • 最後 :

    因爲我的水平有限,博文中不免有錯誤或表達不許確之處,歡迎各位大佬批評指正。若有更好的方法,歡迎評論區留下你的高見,歡迎轉載轉發,記得註明出處。碼字不易,若有幫助,歡迎打賞一杯熬夜咖啡,謝謝老闆~~~

相關文章
相關標籤/搜索