動態規划算法 – 0-1 背包問題 Python 講解| Dynamic Programming Algorithm – 0-1 knapsack problem explained in Python

0-1 背包問題 Python講解

問題描述

有N個物品, 每個物品Cost為C[i], Value為W[i], 其中 i 屬於 [0, …, N-1]
有一個書包, 容量為V
求書包能裝下的最大Value

為什麼叫0-1背包?

因為每個物品只有2選擇: 不用(0)或者用(1). 物品不能重複使用.

暴力解法

暴力求解就是把N個物品的所有子集(Power Set)求出來, 然後看看所有Cost加起來是不是 <= 背包容量 V, 對滿足以上條件的, 選出最大的Value.
子集一共有2^N個, 所以需要O(2^N)時間.
用DFS(深度優先搜索)的話, 需要O(N)空間

2維DP解法

這是一種背包固定套路, 首先初始化一個2維矩陣dp, N+1行, V+1列.

用 Python 表達出來就是

row = [0] * (1 + V)
dp = []
for i in range(N + 1):
    dp.append(list(row))

dp[i][v] 代表的意義是第0到第i-1個物品 (前i個物品), 容量為v的背包, 能放的最大價值
填格子的時候我們從左到右按行掃描, 也就是對一個特定物品, 第i-1個, 掃描所有可能的容量. 到第i-1個物品的時候, 容量為v的時候, 無非兩種選擇:
1. 把這個物品放進去, 那麼v-C[i]需要大於或等於0, 否則放不下, 新的最大value為 dp[i-1][v-C[i]] + W[i]
2. 不把這個物品放進去. 為什麼不放? 因為放不進了啊. value為 dp[i-1][v]

dp[i][v]需要放這兩種情況中value大的那種. 用python表達就是, 先把dp[i][v]設成不放的情況, 如果可以放的話(v-C[i] >= 0), 並且放了Value增加了, 就換成放的情況. 用Python表達出來就是

for i in range(1, N + 1):
    for v in range(1, V + 1):
        dp[i][v] = dp[i - 1][v]
        if v - C[i] >= 0:
            dp[i][v] = max(
                dp[i][v], 
                dp[i - 1][v - C[i - 1]] + W[i - 1]
            )

注意下標有點tricky. dp[i][v]代表的意義是前i個物品, 所以最後一個物品的index是i-1 (物品下標從0開始). 這就是為什麼要用C[i - 1], W[i - 1]. 很多純理論算法用的是1 base的index, 轉換成code的時候還有點麻煩. 我這裡用的就直接是Python或大部分語言能跑的真code. dp[i][j] 賦值部分就是所謂的DP轉移方程.

最後把初始化, 循環和轉移方程連在一起, 就可以用 Python 完整的表達出來

def maxKnapsackValue(N, C, W, V):
    # init dp
    row = [0] * (1 + V)
    dp = []
    for i in range(N + 1):
        dp.append(list(row))

    for i in range(1, N + 1):
        for v in range(1, V + 1):
            dp[i][v] = dp[i - 1][v]
            if v - C[i - 1] >= 0:
                dp[i][v] = max(
                    dp[i][v], 
                    dp[i - 1][v - C[i - 1]] + W[i - 1]
                )
    return dp[N][V]

以上就是 Python 版本的0-1背包問題的2維DP套路解法

2維DP解法應用

以上總結的2維DP解法的模版可以直接應用在Leetcode 416上, 詳細請看這篇文章.

參考資料

崔添翼 背包問題九講 2.0 原文有些細節講得不清楚, 而且有些偽代碼轉換成真代碼時候有些問題, 本文用能run的真代碼並試圖把細節交代清楚.

Leave a Comment

Your email address will not be published.