10 1 2 使用記憶化快取結果

2021-06-27 17:17:28 字數 1669 閱讀 5531

10.1.2 使用記憶化快取結果

記憶化(memoization),可以描述為快取函式呼叫的結果,聽起來可能有點複雜,但是,技術非常簡單。正如我們前面提到的那樣,在函式式程式設計中,大多數函式是沒有***的,因此,如果我們用相同的引數值,兩次呼叫同乙個函式,得到的結果相同。

如果我們要得到與上一次相同的結果,為什麼還要麻煩去再一次執行函式呢?相反,我們可以快取這個結果。如果我們把第一次呼叫的結果,以字典方式儲存起來,第二次呼叫時,就不必重新計算值了;可以從字典中讀取結果,馬上返回。清單 10.3 顯示了乙個函式,計算兩個整數的和。

計算 10.3 使用了記憶化的加法(f# interactive)

> open system.collections.generic;;

> let add******(a, b) =     [1]

printfn"adding %d + %d" a b     <--輸出除錯資訊

a + b 

val add****** : int * int –> int

> let add =     [2]

let cache = newdictionary<_, _>() 

(fun x –>     <-- 建立使用快取的函式

matchcache.trygetvalue(x) with    |

|true, v –> v                    |從快取中讀取值

| _-> let v = add******(x)         | 或者計算

cache.add(x, v)                 |

v)                            |

val add : (int * int -> int)

> add(2,3);;    |

adding 2 + 3    |

val it : int = 5   | add****** 只執行一次

> add(2,3);;     |

val it : int = 5   |

清單 10.3 的第一部分是通常的加法函式[1],稍許有一點變化,把執行過程記錄在控制台上;如果沒有這一點,我們就不知道在原始版本和記憶化版本之間,有什麼明顯的差異,因為從效率的變化上看,在這個示例中實在是太小了。

實現有快取功能的函式為 add[2];它使用 .net 中的字典(dictionary)型別來儲存快取的結果。快取被宣告為區域性值,在用 lambda 表示式中使用,指定給 add 值。我們使用的模式,與第八章討論使用閉包捕獲可變狀態的相類似。在這裡,cache 值也是可變的(因為,字典是可變的雜湊表),也由閉包捕獲。問題是,對於所有呼叫 add 函式,我們需要使用相同的 cache 值,所以,必須在函式之前宣告,但我們又不希望使它成為全域性值。

函式的最後一部分是 lambda 函式本身。當結果沒有快取時,它才使用 add****** 函式。從f# interactive 會話中可以看到,進行計算的函式,只在第一次呼叫時才執行。

這種方法比尾遞迴的應用更廣泛,沒有任何***1 的函式都適用。因此,在c# 3.0 中也可以成功使用。在下一小節中,我們將使用 c# 3.0,把這段**寫成更通用的版本。

1 這可能有些令人困惑,因為在前面清單中的函式,是有***(輸出到螢幕)的。這是一種「軟***」(soft side effect),可以放心地忽略。核心要求是,結果應該只依賴於傳遞給函式的引數值。

記憶化搜尋

演算法上依然是搜尋的流程,但是搜尋到的一些解用 動態規劃 的那種思想和模式作一些儲存。一般說來,動態規劃總要遍歷所有的狀態,而搜尋可以排除一些無效狀態。更重要的是搜尋還可以剪枝,可能剪去大量不必要的狀態,因此在空間開銷上往往比動態規劃要低很多。記憶化演算法在求解的時候還是按著自頂向下的順序,但是每求...

記憶化搜尋

記憶化搜尋 演算法上依然是搜尋的流程,但是搜尋到的一些解用動態規劃的那種思想和模式作一些儲存。記憶化演算法在求解的時候還是按著自頂向下的順序,但是每求解乙個狀態,就將它的解儲存下來,以後再次遇到這個狀態的時候,就不必重新求解了。例1.題目描述 給從左至右排好隊的小朋友們分糖果,要求 1.每個小朋友都...

記憶化搜尋

原文 感謝作者。一.動態規劃 動態規劃 dynamic programming 與 分治思想 有些相似,都是利用將問題分 為子問題,並通過合併子問題的解來獲得整個問題的解。於 分治 的不同之處在 於,對於乙個相同的子問題動態規劃演算法不會計算第二次,其實現原理是將每乙個計算過的子問題的值儲存在乙個表...