動態規劃——基礎

記憶化與製表的比較(memoization vs tabulation)

記憶化(由上而下)與製表(由下而上)是實作同一個動態規劃的兩種方式——相同的狀態、相同的轉移、相同的基底情況、相同的最終答案。它們只在「表格如何被填滿」上不同。由上而下寫一個帶快取的遞迴函式,讓遞迴決定要算什麼、何時算;由下而上寫迴圈,以你選定的固定順序填表。在兩者之間取捨是工程決策,而非正確性問題:一條正確的遞迴不論哪種寫法都給出相同答案。

要把由上而下轉成由下而上,你得把隱含的相依順序變明確。在記憶化版本裡,solve(state) 會遞迴進它所依賴的狀態,所以呼叫順序是相依圖「狀態 X 需要狀態 Y」的某個有效拓樸順序。製表只是把一個尊重同一張圖的迴圈順序寫死——當較大的狀態依賴較小的狀態時,通常就是索引遞增。反方向轉換同樣直接:拿掉迴圈的明確順序,讓遞迴加快取重新發現它。因為兩者實現的是同一條遞迴,它們有相同的漸進執行時間,即(狀態數)乘以(每次轉移的工作)。

何時該選哪一個?當實際可達的只是稀疏的一小撮狀態時(其餘自動跳過)、當自然的遞迴結構遠比手推的迴圈順序好寫時、或當計算順序難以明說時,由上而下勝出。當你需要極致速度(無呼叫開銷)、當遞迴對大 n 會讓堆疊溢位時、尤其當你想藉由只保留最近填好格子的滑動視窗來縮減記憶體時,由下而上勝出——這種滾動陣列技巧由上而下不易做到,因為它隨時可能回頭造訪舊狀態。

對兩個長度 n 字串的最長共同子序列,兩者都以 O(n^2) 時間填一張 n 乘 n 的表。由下而上可把表縮成兩列(O(n) 空間),因為它逐列填,永遠不需要更舊的列。由上而下保留整張表,但會跳過任何對齊都觸及不到的格子。

同一條遞迴、同樣的時間;由下而上能用滾動陣列省空間,由上而下則跳過觸及不到的狀態。

它們不是給出不同答案的不同演算法——只是同一條遞迴的不同實作。若由上而下和由下而上結果不一致,那是有臭蟲(常是基底情況寫錯,或迴圈順序讀到過時值),而非什麼深刻的差別。

又称
top-down vs bottom-up由上而下與由下而上