動態規劃背包

背包問題在於如何在有限的背包承重能力下,裝入最大的物品價值。

物品的重量與價錢

物品 重量
第1個物品 1kg
第2個物品 4kg
第3個物品 3kg
物品 價錢
第1個物品 1500
第2個物品 3000
第3個物品 2000

預設為0

每一列(row),代表第幾個物品。
每一欄(column),代表背包可放重量。
第0個物品,代表沒有東西,不管背包承重多少公斤,全部都是0。

物品\背包重量 0kg 1kg 2kg 3kg 4kg
第0個物品 0 0 0 0 0
第1個物品          
第2個物品          
第3個物品          

當背包承重重量為0kg,不管是第幾個物品都不能放入背包,所以填0。

物品\背包重量 0kg 1kg 2kg 3kg 4kg
第0個物品 0 0 0 0 0
第1個物品 0        
第2個物品 0        
第3個物品 0        

第一個物品

程式是從第1個物品,與背包承重能力1kg開始,不是第0個物品跟背包0kg開始。
第1個物品重量是1kg,價值是1500元。
第1個物品重量1kg,代表背包1至4公斤,都可放入,而且1500大於上一列(row)中1至4公斤的0元。
所以在1至4公斤,都填入物品的價錢。

物品\背包重量 0kg 1kg 2kg 3kg 4kg
第0個物品 0 0 0 0 0
第1個物品 0 1500 1500 1500 1500
第2個物品 0        
第3個物品 0        

第二個物品

第2個物品重量是4kg,價值是3000元。
物品重量4kg,代表背包1至3公斤,都不能放入。
不能放入的情況下,只能填上一列(row)的價錢,上一列代表可以放入的最大價值。
背包承重4公斤,可以放入第二個物品,表格填上第二個物品價錢3000。

物品\背包重量 0kg 1kg 2kg 3kg 4kg
第0個物品 0 0 0 0 0
第1個物品 0 1500 1500 1500 1500
第2個物品 0 1500 1500 1500 3000
第3個物品 0        

第三個物品

第3個物品重量是3kg,價值是2000元。
物品重量3kg,代表背包1至2公斤,都不能放入。
背包1至2公斤不能放入的情況下,只能填上一列(row)的價錢,上一列代表可以放入的最大價值。
背包承重3公斤,第3物品重量3公斤,而且第三個物品的價錢2000,比上一列1500大,填上第三個物品價錢2000。
背包承重4公斤,背包承重4公斤減第三個物品3公斤,還有1公斤的剩餘重量可裝1公斤的東西。
到上一列(row),尋找背包承重1公斤的價錢1500。
第三個物品的價格2000 + 1公斤(第1個物品)的價格1500,二者相加為3500,大於上一列4公斤3000元的價格,把3500填入。

物品\背包重量 0kg 1kg 2kg 3kg 4kg
第0個物品 0 0 0 0 0
第1個物品 0 1500 1500 1500 1500
第2個物品 0 1500 1500 1500 3000
第3個物品 0 1500 1500 2000 3500

公式

物品重量 > 背包承重重量,使用上一列(row)可以放入的最大價值。

1
2
3
4
// i是第幾個物品 j是背包承重重量
if (weight[i] > j) {
  dp[i][j] = dp[i - 1][j];
}

有剩餘重量

物品重量 <= 背包承重重量:
背包重量 - 物品重量 = 有剩餘重量。
去前一列找剩餘重量的最大價值 + 目前物品價值 = 相加價值,若相加價值大於上一列row的最大價值,填入相加價值。

1
2
3
4
5
6
7
8
// i是第幾個物品 j是背包承重重量
// restW是剩餘重量
int restW = j - weight[i];
totalValue = value[i] + dp[i-1][restW];
// dp[i-1][j]代表上一列最大價值
if (totalValue > dp[i-1][j]) {
	dp[i][j] = totalValue;
}

沒剩餘重量

背包重量 - 物品重量 = 0
若物品價值大於上一列row的最大價值,填入物品價值。

1
2
3
4
5
// i是第幾個物品 j是背包承重重量
// 物品價值 大於 上一列最大價值
if (value[i] > dp[i-1][j]) {
	dp[i][j] = value[i];
}

顯示最大價值是放入那些物品

設計一個跟dp二維陣列一模一樣大小的二維陣列。
記錄最大價值的i與j。

1
int[][] mark = new int[num + 1][packpackW + 1];

存入最大價格時,把mark[i][j]設為1。

1
mark[i][j] = 1;

因為最大價值一定在最後,所以要二維陣列要由後往前找。

1
2
int i = dp.length - 1;
int j = dp[0].length - 1;

剩餘重量 = 背包的承重重量j 減 物品重量,j要移動到上一列(i–),剩餘重量j的位置。

1
2
3
4
5
6
7
8
9
10
while (i > 0 && j > 0) {
  // 找到記錄最大價值的i與j
  if (mark[i][j] == 1) {
    System.out.println("第"+ i + "個物品,由1開始數");
    // 剩餘重量 = 背包的重量j - 物品重量
    j = j - weight[i - 1];
  }
  // 移動到上一列
  i--;
}

完整程式碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public class backpack {
  public static void main(String[] args) {
    int[] weight = {1, 4, 3}; // 物品重量
    int[] value = {1500, 3000, 2000}; // 物品價錢
    int packpackW = 4; //背包重量,最大4公斤
    int num = weight.length; // 物品數量,要與weight value大小一致。
    // row是物品項目,column是背包重量
    // +1是因為有0個物品,0kg背包重量
    int[][] dp = new int[num + 1][packpackW + 1];
    int[][] mark = new int[num + 1][packpackW + 1];
    // i = 1 從第1個物品開始
    for (int i = 1; i < dp.length; i++) {
      // j = 1 從1公斤開始
      for (int j = 1; j < dp[0].length; j++) {
        // weight[i-1]是因為i從1開始,weight[1]代表是陣列索引為1的物品的重量
        // 但我想從索引為0開始weight[1-1] == weight[0]
        if (weight[i - 1] > j) { // 若物品重量超出背包重量
         // 物品重量塞不進背包,使用上一個可以放下背包重量的價錢
          dp[i][j] = dp[i - 1][j];
       } else { // 物品重量能放入背包
          // 上一列的最大價值
          int beforeValue = dp[i - 1][j];
          //背包重量j減物品重量weight[i - 1] = 剩餘重量
          //上一次 剩餘重量的價錢 dp[i - 1][j - weight[i - 1]]
          //物品的價錢 + 上一次 剩餘重量的價錢
          int curValue = value[i - 1] +
              dp[i - 1][j - weight[i - 1]];
          if (curValue > beforeValue) {
            dp[i][j] = curValue;
            mark[i][j] = 1;
          } else {
            dp[i][j] = beforeValue;
          }
       }
      }
    }
    for (int[] arr: dp) {
      System.out.println(Arrays.toString(arr));
    }
    int i = dp.length - 1;
    int j = dp[0].length - 1;
    while (i > 0 && j > 0) {
      if (mark[i][j] == 1) {
        System.out.println("第"+ i + "個物品,由1開始數");
        j = j - weight[i - 1];
      }
      i--;
    }
  }
}
[0, 0, 0, 0, 0]
[0, 1500, 1500, 1500, 1500]
[0, 1500, 1500, 1500, 3000]
[0, 1500, 1500, 2000, 3500]
第3個物品,由1開始數
第1個物品,由1開始數

results matching ""

    No results matching ""