0/1 背包問題

My vault 演算法筆記:0/1 背包問題。

https://www.hello-algo.com/zh-hant/chapter_dynamic_programming/knapsack_problem/#__tabbed_4_6

1. 問題定義

  • 給定 (Given):

    • $n$ 個物品 (items)。

    • 每個物品 $i$ 有一個重量 $w_i$ 和一個價值 $v_i$。

    • 一個背包,其最大可承受重量 (容量) 為 $W$。

  • 限制 (Constraint):

    • 對於每個物品,你只能選擇 拿 (1) 或 不拿 (0)。

    • 不能只拿物品的一部分(這與「部分背包問題」Fractional Knapsack Problem 不同)。

  • 目標 (Goal):

    • 求解在總重量不超過 $W$ 的前提下,能放入背包的最大總價值。

筆記重點:

Greedy (貪婪法) 無法保證得到 0/1 背包問題的最佳解。

此問題具有最佳子結構 (Optimal Substructure),適合使用 DP 求解。

2. Recursive Form (遞迴關係式)

我們使用一個二維陣列(表格)$C[i, k]$ 來定義子問題:

$C[i, k]$ = 考慮前 $i$ 個物品 (item 1 到 $i$),在背包容量上限為 $k$ 時,所能得到的最大總價值。

我們的目標是求出 $C[n, W]$。

遞迴推導:

當我們考慮第 $i$ 個物品(其重量為 $w_i$,價值為 $v_i$)時,有兩種選擇:

  1. 不放第 $i$ 個物品:

    • 這可能是因為不想放,或是因為放不下 ($k < w_i$)。

    • 最大價值會等於「只考慮前 $i-1$ 個物品,且容量為 $k$」時的最大價值。

    • 即 $C[i-1, k]$。

  2. 放第 $i$ 個物品:

    • 這必須在 $k \ge w_i$ (容量足夠) 的前提下才能發生。

    • 總價值會等於「第 $i$ 個物品的價值 $v_i$」加上「考慮前 $i-1$ 個物品,且容量剩下 $k - w_i$」的最大價值。

    • 即 $v_i + C[i-1, k - w_i]$。

關係式總結:

$C[i, k]$ 就是取上述兩種情況中,價值較大的一個。

$$C[i, k] = \begin{cases} 0 & \text{if } i = 0 \text{ or } k = 0 \ C[i-1, k] & \text{if } k < w_i \text{ (放不下第 i 個物品)} \ \max(C[i-1, k], \quad v_i + C[i-1, k - w_i]) & \text{if } k \ge w_i \text{ (可選擇放或不放)} \end{cases}$$

3. 演算法 (Algorithm) - Bottom-up DP

我們可以使用「由下而上」(Bottom-up) 的方式,填滿 $C[i, k]$ 這個表格來求解。

程式碼片段

// 演算法:0/1 Knapsack
// 輸入: n (物品數量), W (總容量), v (價值陣列), w (重量陣列)
// 輸出: 最大價值

// 1. 建立一個 (n+1) x (W+1) 的表格 C
create table C[0..n, 0..W]

// 2. 初始化 Base Case (第 0 列)
// 當沒有物品(i=0)時,任何容量 k 的價值都是 0
for k <- 0 to W:
    C[0, k] <- 0

// 3. 迭代所有物品 i (從 1 到 n)
for i <- 1 to n:
    // 初始化 Base Case (第 0 行)
    // 當容量為 0 (k=0)時,任何物品 i 都放不進,價值為 0
    C[i, 0] <- 0

    // 4. 迭代所有容量 k (從 1 到 W)
    for k <- 1 to W:
        // 取得第 i 個物品的價值 v_i 和重量 w_i
        
        // 情況 1: 當前容量 k < 物品 i 的重量
        if k < w_i:
            // 放不下,價值等於「不放」的情況
            C[i, k] <- C[i-1, k]
        
        // 情況 2: 當前容量 k >= 物品 i 的重量
        else:
            // 比較「不放」和「放」哪個價值高
            C[i, k] <- max(
                            C[i-1, k],                  // 不放第 i 個物品
                            v_i + C[i-1, k - w_i]       // 放第 i 個物品
                        )

// 5. 最終答案
// 考慮了全部 n 個物品,且總容量為 W 時的最大價值
return C[n, W]

4. 複雜度分析 (Complexity Analysis)

  • 時間複雜度 (Time Complexity): $O(nW)$

    • 原因: 來自兩個巢狀迴圈 (nested loop)。外層迴圈跑 $n$ 次 (for $i$),內層迴圈跑 $W$ 次 (for $k$)。
  • 空間複雜度 (Space Complexity): $O(nW)$

    • 原因: 需要一個 $C[0..n, 0..W]$ 的二維表格來儲存子問題的解,表格大小為 $(n+1) \times (W+1)$,也就是額外花費的記憶體。

填表過程

直觀的說

1. 裝得下嗎? ($k < w_i$)

  • 不行: 答案 = 抄樓上 ($C[i-1, k]$)

2. 裝得下。 ($k \ge w_i$)

  • 答案 = 比較下面兩者,選大的:

    • 不拿: 樓上 ($C[i-1, k]$)

    • 拿: $\text{斜上} + \text{新價值}$ ($C[i-1, k - w_i] + v_i$)

「斜上」 就是指:往上一列,再往左 $w_i$ 格。

物品 1:$i=1, w_1=2, v_1=6$

$C[1,k]=\max{C[0,k],6+C[0,k-2]}$

結果列:

$C[1,k]$ 0 1 2 3 4 5
值 0 0 6 6 6 6

物品 2:$i=2, w_2=3, v_2=10$

$C[2,k]=\max{C[1,k],;10+C[1,k-3]}$

結果列:

$C[2,k]$ 0 1 2 3 4 5
值 0 0 6 10 10 16

物品 3:$i=3, w_3=4, v_3=12$

$C[3,k]=\max{C[2,k],12+C[2,k-4]}$

最終列:

$C[3,k]$ 0 1 2 3 4 5
值 0 0 6 10 12 16

完整表格

$C[i,k]$ 0 1 2 3 4 5
$i=0$ 0 0 0 0 0 0
$i=1$ 0 0 6 6 6 6
$i=2$ 0 0 6 10 10 16
$i=3$ 0 0 6 10 12 16

答案:$C[n,W]=C[3,5]=16$

回溯解集合

從 $C[3,5]=16$ 往回:

  1. $C[3,5]=16= C[2,5]$ ⇒ 不選物品 3。移到 $(2,5)$

  2. $C[2,5]=16 \ne C[1,5]=6$ ⇒ 選物品 2。$k\leftarrow 5-3=2$,移到 $(1,2)$

  3. $C[1,2]=6 \ne C[0,2]=0$ ⇒ 選物品 1。$k\leftarrow 2-2=0$,移到 $(0,0)$ 結束

選擇:${1,2}$
總重量:$2+3=5$
總價值:$6+10=16$