動態規劃算法(01揹包問題)

一. 動態規劃算法介紹:

動態規劃算法和分治算法類似,也是將待求解問題分成若干個小問題一步步求解,不同的是,每一個小問題求解過程依賴於上一個小問題的解。動態規劃問題可以通過填表法來得到解,最經典的應用就是揹包問題。

二. 揹包問題:

1. 揹包問題介紹:

揹包問題,就是有一個能裝重量爲X的揹包,現有重量W和價值V各不相同的幾件物品,在不超過揹包容量X的情況下,如何使得揹包內物品的總價值V最大。如果可以裝相同的物品,稱爲完全揹包問題,不可以裝相同的物品,稱爲01揹包問題

2. 填表法推導過程:

假如現有一個揹包容量爲4,現有3件物品,其價值和體積如下表:

物品 重量W 價值V
A 1 15
B 4 30
C 3 20

在物品不能相同的情況下,如何裝才能使總價值最大呢?顯然是把A和C都裝進揹包,可以獲得總價值爲35,這種情況是最優的,那麼是如何推導出來的呢?

我們可以把這個問題分成一個個的小問題來解決,現在的揹包能裝的重量是4,那我們就看看揹包容量爲1、2、3、4的時候,裝東西能產生的最大價值分別是多少。這裏要注意兩點:

  • 分配容量的規則爲最小重量的整數倍,這裏物品最小重量是1,所以分別看揹包容量爲1、2、3、4時裝東西的最大價值;如果最小重量是2,那麼就看揹包容量爲2、4的時候能裝的最大價值。

  • 後一個問題的解依賴於前一個問題的解。

現在用填表法來解決這個揹包問題:

物品\揹包容量 0 1 2 3 4
A
B
C

首先說明一下這個表,0,1,2,3,4表示的是揹包容量,A,B,C表示的是物品,“無”表示的是沒有物品的時候。空出來的格子就填寫當前能夠得到的最大價值。顯然第二行和第二列都是0,第二行表示不裝東西的時候,那麼不管揹包容量是多少,不裝東西價值都是0;第一列表示揹包容量爲0的時候,啥都裝不了,所以價值也是0。

物品\揹包容量 0 1 2 3 4
0 0 0 0 0
A 0
B 0
C 0

那麼接下來就看第三行,即只裝物品A的時候,能夠得到的最大價值。因爲規定了不能放入重複的物品,所以即使容量足夠的情況下也只能放入一件物品A,所以在容量不夠裝A之前,價值是0,能夠裝A之後,價值就是A的價值,即15。

物品\揹包容量 0 1 2 3 4
0 0 0 0 0
A 0 15 15 15 15
B 0
C 0

接下來再看第四行,第四行的時候,不是隻能裝B,之前說的那句話,“後一個問題的解依賴於前一個問題的解”,即第四行的時候,是要考慮裝A的情況,即要依賴第三行。

  • 第四行第三列:容量爲1,沒得選,因爲B的重量是4,只能裝A,所以這裏填15(沒得選的情況,就直接把上一行該列的值複製下來即可);

  • 第四行第四列:容量爲2,也沒得選,複製上一行該列的值;

  • 第四行第五列:容量爲3,還是沒得選,複製上一行該列的值;

  • 第四行第六列:容量爲4,可以選擇裝B,也可以不裝B。怎麼判斷裝不裝呢?看B的價值是否大於該列上一行的值,B的價值是30,而該列上一行的值是15,30更大,所以我們選擇裝B,這裏填入30,而且裝了B之後就沒有剩餘容量了。

物品\揹包容量 0 1 2 3 4
0 0 0 0 0
A 0 15 15 15 15
B 0 15 15 15 30
C 0

來看最後一行:

  • 第五行第三列:容量爲1,沒得選,因爲C的重量是3,裝不下,所以直接複製上一行該列的值,即15;

  • 第五行第四列:容量爲2,也沒得選,複製上一行該列的值;

  • 第五行第五列:容量爲3,可以選擇裝C,也可以選擇不裝複製上一行該列的值,但是C的價值是20,大於15,所以這裏填20,而且裝了C之後沒有剩餘容量了;

  • 第五行第六列:容量爲4,可以選擇裝C,也可以不裝C。不裝的話,就直接複製上一行該列的值,是30;如果裝C,C消耗的容量是3,價值是20,還剩餘1個容量,那麼就去容量爲1的那一列,找一個最大值,是15,因爲20 + 15 = 35 > 30,所以這裏填入35。

物品\揹包容量 0 1 2 3 4
0 0 0 0 0
A 0 15 15 15 15
B 0 15 15 15 30
C 0 15 15 20 35

通過上面這個推導過程可以發現,A對應的這一行就只考慮物品A的情況,B這一行不僅要考慮B,還要考慮A,C這一行就要考慮A和B。當有A、B、C三種物品且揹包容量爲4時,能夠獲得的最大價值就是C這一行,4對應的這一列的值,即35。

3. 總結公式:

我們把上面的第二行第二列開始的部分看成是一個二維數組,如下:

0 0 0 0 0
0 15 15 15 15
0 15 15 15 30
0 15 15 20 35

所以二維數組可以定義成:

int[][] tv = new int[4][5]

tv[i][j]就表示前i個物品,裝入容量爲j的揹包時,能夠獲得的最大價值。
4怎麼來的?總共有3件物品,加上沒有物品的情況,就是4;5怎麼來的?揹包容量被我們拆成了1、2、3、4,再加上容量爲0的情況,就是5。

我們再定義一個數組用來保存物品的重量:

int[] w = {1,4,3}; // 物品的重量

還需要定義一個數組來保存物品的價值:

int[] v = {15,30,20}; // 物品的價值

(1). tv[i][0] = 0,表示第一行都是0;tv[0][j] = 0,表示第一列都是0;

(2). w[i]表示的是第i件物品的重量,v[i]表示的是第i件物品的價值;j是列的索引,第0列表示揹包容量爲0時,第1列表示揹包容量爲1時,所以j表示的是當前揹包的容量。

(3).w[i] > j時,那麼就讓tv[i][j] = tv[i-1][j]。也就是說,第i件物品的重量大於當前揹包的容量時,那麼久直接將上一行該列的那個值複製過來。

(4).w[i] <= j時,那麼就讓tv[i][j] = max{tv[i-1][j], v[i] + tv[i-1][j-w[i]]}。條件就是第i件物品的重量小於等於揹包容量時,此時有兩種情況,一個是裝,一個是不裝,怎麼判斷裝不裝呢,那就判斷裝能獲得的總價值更大還是不裝能獲得的總價值更大。

  • 如果不裝第i件物品,能獲得的最大價值那就和上一行該列的值一樣,即tv[i-1][j]

  • 如果裝第i件物品,能夠獲得的最大價值就是第i件物品的價值加上裝了第i件物品後剩餘容量能夠獲得的價值。v[i]是第i件物品的價值,怎麼理解tv[i-1][j-w[i]]?首先看j-w[i],意思就是當前揹包容量減去第i件物品的重量,那也就是當前揹包容量下如果裝第i件物品後剩餘的容量,所以tv[i-1][j-w[i]]的意思就是,去上一行找揹包容量爲j-w[i]時的那個值,也就是當前揹包裝了第i件物品後剩餘容量能夠獲得的最大價值;v[i] + tv[i-1][j-w[i]]就是如果裝第i件物品能夠獲得的最大價值。

  • 經過上面的分析,tv[i][j] = max{tv[i-1][j], v[i] + tv[i-1][j-w[i]]}就很好理解了,當揹包容量爲j時,裝前i件物品能夠獲得的最大價值就是在裝與不裝兩種情況中取最大值。

3. 代碼實現:

public class BagProblem {
    
    public static void main(String[] args) {
        int[] w = {1,4,3}; // 物品的重量
        int[] v = {15,30,20}; // 物品的價值
        int m = 4; // 揹包的容量
        System.out.println(maxValue(w, v, m));
    }
    
    public static int maxValue(int[] w, int[] v, int m) {
        int n = w.length; // 物品個數
        int[][] tv = new int[n+1][m+1];
        for(int i=0; i<v.length; i++) {
            tv[i][0] = 0; // 第一列全部設置爲0
        }
        for(int i=0; i<tv[0].length; i++) {
            tv[0][i] = 0; // 第一行全部設置爲0
        }
        for(int i=1; i<tv.length; i++) {
            for(int j=1; j<tv[0].length; j++) {
                if (w[i-1] > j) { // 循環中i從1開始,所以要減1
                    tv[i][j] = tv[i-1][j];
                } else {
                    // 循環中i從1開始,所以w和v中的i要減1
                    tv[i][j] = Math.max(tv[i-1][j], v[i-1] + tv[i-1][j-w[i-1]]);
                }
            }
        }
        return tv[n][m];
    }

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