天天看點

java動态規劃之背包問題

想了解更多資料結構以及算法題,可以關注微信公衆号“資料結構和算法”,每天一題為你精彩解答。也可以掃描下面的二維碼關注

java動态規劃之背包問題

描述

背包問題是動态規劃中最經典的一道算法題。背包問題的種類比較多,我們先來看一個最簡單的背包問題-基礎背包。他是這樣描述的。

有N件物品和一個容量為V的包,第i件物品的重量是w[i],價值是v[i],求将哪些物品裝入背包可使這些物品的重量總和不能超過背包容量,且價值總和最大。我們先來舉個例子分析一下

舉例分析

假設我們背包可容納的重量是4kg,有3樣東西可供我們選擇,一個是高壓鍋有4kg,價值300元,一個是風扇有3kg,價值200元,最後一個是一雙運動鞋有1kg,價值150元。問要裝哪些東西在重量不能超過背包容量的情況下價值最大。如果隻裝高壓鍋價值才300元,如果裝風扇和運動鞋價值将達到350元,是以裝風扇和運動鞋才是最優解,我們來畫個圖分析一下

java動态規劃之背包問題
java動态規劃之背包問題
java動态規劃之背包問題
java動态規劃之背包問題
java動态規劃之背包問題
java動态規劃之背包問題
java動态規劃之背包問題
java動态規劃之背包問題
java動态規劃之背包問題
java動态規劃之背包問題
java動态規劃之背包問題
我們上面選擇的順序是:運動鞋→高壓鍋→風扇,如果我們改變選擇的順序,結果會不會改變,比如我們選擇的順序是:風扇→運動鞋→高壓鍋,我們還是來畫個圖看一下
java動态規劃之背包問題

我們發現無論選擇順序怎麼改變都不會改變最終的結果。

資料測試:

public static void main(String[] args) {
     System.out.println("最終結果是:" + packageProblem1());
 }
 
 public static int packageProblem1() {
     int packageContainWeight = 4;//包最大可裝重量
     int[] weight = {1, 4, 3};//3個物品的重量
     int[] value = {150, 300, 200};//3個物品的價值
     int[][] dp = new int[weight.length + 1][packageContainWeight + 1];
    for (int i = 1; i <= value.length; i++) {
        for (int j = 1; j <= packageContainWeight; j++) {
            if (j >= weight[i - 1]) {
                dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weight[i - 1]] + value[i - 1]);
            } else {
                dp[i][j] = dp[i - 1][j];
            }
        }
    }
    Util.printTwoIntArrays(dp);//這一行僅做列印觀測資料,也可以去掉
    return dp[weight.length][packageContainWeight];
}
           

運作結果

java動态規劃之背包問題
和我們上面分析的完全一緻。(為了測試友善,這裡的所有資料我都是寫死的,我們也可以把這些資料提取出來,作為函數參數傳進來。)

空間優化:

其實這題還可以優化一下,這裡的二維數組我們每次計算的時候都是隻需要上一行的數字,其他的我們都用不到,是以我們可以用一維空間的數組來記錄上一行的值即可,但要記住一維的時候一定要逆序,否則會導緻重複計算。我們來看下代碼
public static int packageProblem2() {
     int packageContainWeight = 4;
     int[] weight = {1, 4, 3};
     int[] value = {150, 300, 200};
     int[] dp = new int[packageContainWeight + 1];
     for (int i = 1; i <= value.length; i++) {
         for (int j = packageContainWeight; j >= 1; j--) {
             if (j - weight[i - 1] >= 0)
                 dp[j] = Math.max(dp[j], dp[j - weight[i - 1]] + value[i - 1]);
        }
        Util.printIntArrays(dp);
        System.out.println();
    }
    return dp[packageContainWeight];
}
           
注意:
我們看到第7行在周遊重量的時候采用的是逆序的方式,因為第9行在計算dp[j]的值的時候,數組後面的值會依賴前面的值,而前面的值不會依賴後面的值,如果不采用逆序的方式,數組前面的值更新了會對後面産生影響。

運作結果

java動态規劃之背包問題

C++:

#include<iostream>
 #include <algorithm>
 
 using namespace std;
 
 int main()
 {
     int weight[] = { 1,4,3 };
     int value[] = {150, 300, 200 };
    int packageContainWeight = 4;
    int dp[4][5]= { { 0 } };
    for (int i = 1; i <4 ; i++)
    {
        for (int j = 1; j < 5; j++)
        {
            if (j >= weight[i - 1])
                dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i - 1]] + value[i - 1]);
            else
                dp[i][j] = dp[i - 1][j];
        }
    }

    for (int i = 0; i < 4; i++)
    {
        for (int j = 0; j < 5; j++)
        {
            cout << dp[i][j] << ' ';
        }
        cout << endl;
    }

    return 0;
}
           
運作結果
java動态規劃之背包問題

遞歸寫法:

除了上面的兩種寫法以外,我們還可以使用遞歸的方式,代碼中有注釋,有興趣的可以自己看,就不在詳細介紹。
int[] weight = {1, 4, 3};//3個物品的重量
 int[] value = {150, 300, 200};//3個物品的價值
 
 // i:處理到第i件物品,j可容納的重量
 public int packageProblem3(int i, int j) {
     if (i == -1)
         return 0;
     int v1 = 0;
     if (j >= weight[i]) {//如果剩餘空間大于所放的物品
        v1 = packageProblem3(i - 1, j - weight[i]) + value[i]; //選第i件
    }
    int v2 = packageProblem3(i - 1, j);//不選第i件
    return Math.max(v1, v2);
}