已發布·持續改進
Algorithm
動態規劃(DP)透過儲存並重複使用重疊子問題的答案,把指數級的搜尋變成有效率的填表計算,用來解決最佳化與計數問題。
動態規劃(Dynamic Programming,DP)是一種演算法設計方法:把問題拆成較小的子問題,每個子問題只求解一次並記錄答案,之後再遇到同一個子問題時直接取用記錄的結果。它由理查·貝爾曼在 1950 年代系統化提出,適用於子問題彼此重疊(重疊子問題),而且整體最佳解可以由子問題的最佳解組成(最佳子結構)的情況。
許多用單純遞迴或暴力列舉需要指數時間的問題,借助動態規劃可以在與狀態數成正比的時間內解決。檔案比較工具 diff、拼字檢查中的編輯距離、DNA 序列比對、語音辨識中的維特比演算法、資料庫查詢最佳化器的連接順序選擇等,都在使用動態規劃。它也是技術面試與程式競賽中最常出現的主題之一。
學習時建議先用費氏數列理解記憶化遞迴(由上而下)與遞推填表(由下而上)的差別,再依序練習經典問題:找零錢、0/1 背包、最長遞增子序列、最長共同子序列與編輯距離。每一題都先寫出狀態、轉移式、邊界條件與計算順序,親手填一張小表驗證轉移式,再開始寫程式。
同一個子問題多次出現時只計算一次並保存結果,把重複計算變成查表。
整體最佳解必須能由子問題的最佳解組合而成,這個性質是轉移式成立的依據。
可以由上而下替遞迴加上快取(functools.cache),也可以由下而上用迴圈填表。
以決定答案所需的最少參數定義狀態;若只依賴上一列,可以用滾動陣列節省記憶體。
knapsack 以只有一列的表由下而上求解 0/1 背包問題,容量由大到小走訪,確保每件物品最多使用一次。lcs 以 functools.cache 做記憶化,由上而下計算兩個字串的最長共同子序列長度。執行 python dynamic_programming.py 會輸出 9 和 4。
dynamic_programming.py
from functools import cache
def knapsack(weights, values, capacity):
"""0/1 knapsack: best total value within capacity (bottom-up, one row)."""
best = [0] * (capacity + 1)
for w, v in zip(weights, values):
for c in range(capacity, w - 1, -1): # high to low: each item used at most once
best[c] = max(best[c], best[c - w] + v)
return best[capacity]
def lcs(a, b):
"""Length of the longest common subsequence (top-down, memoized)."""
@cache
def solve(i, j):
if i == len(a) or j == len(b):
return 0
if a[i] == b[j]:
return 1 + solve(i + 1, j + 1)
return max(solve(i + 1, j), solve(i, j + 1))
return solve(0, 0)
print(knapsack([1, 3, 4, 5], [1, 4, 5, 7], 7)) # 9
print(lcs("ABCBDAB", "BDCABA")) # 4
python dynamic_programming.py共六章,帶你從安裝一步步認識 動態規劃 的核心概念。
在這裡提問、分享經驗,交流關於 動態規劃 的看法。
還沒有討論。來發起第一則吧。
0 則留言
登入 · 登入後即可留言。
來留下第一則留言吧。