揹包問題Java詳解版

資料:B站揹包九講:https://www.bilibili.com/video/BV1qt411Z7nE
對應訓練題:https://www.acwing.com/problem/

01揹包問題

在這裏插入圖片描述
此問題解法來自揹包九講,

未優化

狀態:f[i][j]表示前 i 個物品,總體積爲 j 的最大價值

所以就有兩種方式,選當前物品放入揹包和不選當前物品放入揹包

即不選當前物品放入揹包 :f[i-1][j],直接拿上一個物品的價值即可,體積不變

或者選當前物品放入揹包,:f[i-1][j-v[i]]+w[i],需要減去當前物品的體積 再加上當前物品價值

當前i,j即取二者最大值

狀態轉移:f[i][j] = Math.max(f[i-1][j],f[i-1][j-v[j]]+w[i])

初始條件,f[0][0]=0

具體代碼



public class Main {
 public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int N = sc.nextInt();
        int M = sc.nextInt();
        int[] Value = new int[N+1];
        int[] Weight = new int[N+1];
        //從1開始,0代表揹包內容爲0時的最大價值
        for(int i = 1;i<=N;i++){
            Weight[i] = sc.nextInt();
            Value[i] = sc.nextInt();
        }
        System.out.println(packageQuestion2(N,M,Weight,Value));

    }
    public static int packageQuestion(int N,int M,int[] Weight,int[] Value){
        // dp狀態方程
        int[][] dp = new int[N+1][M+1];
        // dp[i][j]表示前i個物品,體積是j的,最大的總價值
        //遍歷所有物品
        for(int i = 1;i<=N;i++){
            // 從0開始遍歷揹包容量
            for(int j = 0;j<=M;j++){
              //  dp[i][j]表示前i個物品,體積是j的,最大的總價值
                //如果當前揹包容量大於等於當前元素的體積,就可以裝
                if(j>=Weight[i]){
                    //dp[i-1][j]表示不選當前元素,表示當前容量下前i個元素的價值等於第i-1個元素的價值
                    //dp[i-1][j-Weight[i]]+Value[i]表示選擇當前元素,即:當前元素針對當前揹包大小的價值等於(揹包現容量
                    //-當前元素的體積)的價值加上當前元素的價值,
                    //這裏dp[i-1][j-Weight[i]]+Value[i],爲啥要用i-1的原因是,如果用當前i,其實算當前i的時候已經對當前元素進行處理了,
                    //如果用i,會處理兩次,用i-1纔是處理針對當前層的上一層的最優
                    //上面是通俗一點的解釋,真正的解釋是:dp狀態轉移方程 都是由上一層來遞推到當前層而不是直接在當前層上處理當前層的信息
                    dp[i][j] = Math.max(dp[i-1][j],dp[i-1][j-Weight[i]]+Value[i]);
                }else{
                    //當前揹包容量小於當前元素的體積,針對當前元素當前容量不能裝,只能是上一個元素的當前容量的價值
                    dp[i][j] = dp[i-1][j];
                }
            }

            //當前循環結束後,dp二維數組所組成的二維矩陣,每一層即每個元素的最後一個元素都是針對於當前元素及之前的最優解
            //所以整體最優解就是矩陣的右下角
        }
        return dp[N][M];
    }
}


空間優化

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Scanner;

public class Main {

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int N = sc.nextInt();
        int M = sc.nextInt();
        int[] Value = new int[N+1];
        int[] Weight = new int[N+1];
        //從1開始,0代表揹包內容爲0時的最大價值
        for(int i = 1;i<=N;i++){
            Weight[i] = sc.nextInt();
            Value[i] = sc.nextInt();
        }
        System.out.println(packageQuestion2(N,M,Weight,Value));

    }
    //空間優化
     public static int packageQuestion1(int N,int M,int[] Weight,int[] Value){
        //空間優化,即二維改一維,這裏只需初始化大小爲M+1即可,即揹包大小,每一個元素表示當前重量下的最大價值
        int[] dp = new int[M+1];
        //仍然是遍歷所有物品
        for(int i = 1;i<=N;i++){
            //這裏從最大的揹包數量開始遍歷
            for(int j = M;j>=Weight[i];j--){
                //因爲這裏是一維的,如果不選當前元素,則保持不變即可,因爲不變即和之前保持一致
                // 如果要加上當前元素,則仍然按照dp[j-Weight[i]]+Value[i]的來,不過dp[j-Weight[i]]一定是上一層的,根據未優化來的話,
                //就是這層的j-Weight[i]還未被修改,所以這裏獲得的值也一定是上一層的,注意始終只有一層,沒修改之前就是上層的
                dp[j] = Math.max(dp[j],dp[j-Weight[i]]+Value[i]);
            }
        }
        //同樣,這裏得到的右下角也一定是結果
        return dp[M];
    }
}

完全揹包問題

b站上大神講的實在看不懂,所以找了個簡單的,此題解來源於 https://blog.csdn.net/u013885699/article/details/80254961


 
public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int N = sc.nextInt();
        int M = sc.nextInt();
        int[] Value = new int[N+1];
        int[] Weight = new int[N+1];
        //從1開始,0代表揹包內容爲0時的最大價值
        for(int i = 1;i<=N;i++){
            Weight[i] = sc.nextInt();
            Value[i] = sc.nextInt();
        }
        System.out.println(packageQuestion2(N,M,Weight,Value));

    }
public static int packageQuestion2(int N,int M,int[] Weight,int[] Value){
        int[][] dp = new int[N+1][M+1];
        for(int i = 1;i<=N;i++){
            for(int j =0;j<=M;j++){
                //這之前和上面的01揹包問題沒區別
                //然後這裏因爲多了無限數量的條件,所以這裏要嘗試加入可能的所有的物體的數量
                for(int k = 0;k<=M/Weight[i];k++){
                    //如果當前揹包容量可以放下當前數量的物品
                    if(j>=k*Weight[i]){
                        //同樣,這裏兩種情況,一種是不加當前值,那就是上一層的值即dp[i-1][j]
                        //要加當前值,由於這裏可以無限選,所以要加上可能的所有值即
                        //dp[i-1][j-k*Weight[i]]爲上一層的不加上當前物品的值和數量的最大值,最後加上當前物品的值和數量的最大值
                        //即 dp[i][j]表示對在大於等於i之前的元素的容量爲j的最大價值
                        dp[i][j] = Math.max(dp[i-1][j],dp[i-1][j-k*Weight[i]]+k*Value[i]);
                    }else{
                        dp[i][j] = dp[i][j];
                    }
                }
            }
        }
        return dp[N][M];
    }
}
 

這裏用 O(n³)的時間複雜度會超時,所以我們需要優化算法
O(n²)時間複雜度
解法來源:https://www.acwing.com/video/945/
還是大神講得好琢磨琢磨就差不多了

public class Main {

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int N = sc.nextInt();
        int M = sc.nextInt();
        int[] Value = new int[N+1];
        int[] Weight = new int[N+1];
        //從1開始,0代表揹包內容爲0時的最大價值
        for(int i = 1;i<=N;i++){
            Weight[i] = sc.nextInt();
            Value[i] = sc.nextInt();
        }
        System.out.println(packageQuestion3(N,M,Weight,Value));

    }
    public static int packageQuestion3(int N,int M,int[] Weight,int[] Value){
        int[][] dp = new int[N+1][M+1];
        for(int i = 1;i<=N;i++){
            for(int j = 1;j<=M;j++){
                dp[i][j] = dp[i-1][j];
                if(j>=Weight[i]){
                    dp[i][j] = Math.max(dp[i-1][j],dp[i][j-Weight[i]]+Value[i]);
                }
            }
        }
        return  dp[N][M];
    }
    
}

爲什麼可以優化成O(n²),先看一組數學式子:

01揹包問題遞推方程是這樣的:dp[i][j] = Math.max(dp[i-1][j],dp[i-1][j-Height[i]]+Value[i])

因爲01揹包問題是非黑即白只有兩種狀態,所以在這兩種狀態裏找最大值即可

而完全揹包問題n個物品n個狀態,遞推方程爲:

某狀態A: dp[i][j] = Math.max(dp[i-1][j],dp[i-1][j-Height[i]]+Value[i],dp[i-1][j-2Height[i]]+2Value[i])…,dp[i-1][j-nHeight[i]]+nValue[i]

另一個狀態B:dp[i][j-Height[i]] = Math.max(dp[i-1][j-Height[i]],dp[i-1][j-Height[i]]+Value[i],dp[i-1][j-2Height[i]]+2Value[i]…,dp[i-1][j-n+1Height[i]]+n+1Value[i])

對比上面兩式,可以發現B式每一項 k 都比A式 k-1 項的多出了一個Value[i],所以第一式可以寫成這個

A = max(dp[i-1][j],B+Value[i]);

即:dp[i][j] = Math.max(dp[i-1][j],dp[i][j-Height[i]]+Value[i]),即通過等式代換將三個for嵌套優化成了兩個for
然後做空間優化

public static int packageQuestion4(int N,int M,int[] Weight,int[] Value){
        int[] dp = new int[M+1];
        for(int i = 1;i<=N;i++){
            for(int j = Weight[i];j<=M;j++){
                dp[j] = Math.max(dp[j],dp[j-Weight[i]]+Value[i]);
            }
        }
        return  dp[M];
    }

同樣和01揹包問題類似,我們只需要一個一維dp數組,我們將揹包從大到小遍歷,這樣就能保證在計算大的那一項時,dp[j-Weight[i]]在當前 層得到的結果一定是還沒有被計算過,也就是保留着上一層的計算結果,所以不需要用二維dp來記錄每層的最優解,只需要用倒序遍歷,針對於上一層的最優解直接可以用一維且未被當前層處理的dp元素來遞推,所以這個二維改一維能對算法進一步做空間優化

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章