直接的風格:
let rec quicksort list =
match list with
| [] -> []
| [element] -> [element]
| pivot::rest ->
let left, right = List.partition (fun element -> element < pivot) rest in
let sorted_left = quicksort left in
let sorted_right = quicksort right in
sorted_left @ [pivot] @ sorted_right
我的第一個,天真的翻譯是非常相似的Laurent的版本,除了縮進有點古怪,使與延續呼籲顯然是真正的一種結合:
let rec quicksort list cont =
match list with
| [] -> cont []
| element::[] -> cont [element]
| pivot::rest ->
let left, right = List.partition (fun element -> element < pivot) rest in
quicksort left (fun sorted_left ->
quicksort right (fun sorted_right ->
cont (sorted_left @ [pivot] @ sorted_right)))
let qsort li = quicksort li (fun x -> x)
與Laurent相反,我發現很容易檢查到cont
沒有被遺忘:從直接類型轉換的CPS函數具有連續線性使用的性質,在每個分支中只有一次,在尾部位置。很容易檢查,沒有這樣的電話被遺忘。但事實上,對於大多數quicksort運行(假設您因爲您不是倒黴或您首先對輸入進行了混洗而獲得粗略的對數行爲),調用堆棧並不是問題,因爲它只是以對數形式增長。更令人擔憂的是經常調用@
,它的左邊參數是線性的。一個常見的優化技術是定義函數不爲返回清單,但作爲「添加輸入到累加器列表」:
let rec quicksort list accu =
match list with
| [] -> accu
| element::[] -> element::accu
| pivot::rest ->
let left, right = List.partition (fun element -> element < pivot) rest in
let sorted_right = quicksort right accu in
quicksort left (pivot :: sorted_right)
let qsort li = quicksort li []
當然,這可以再次變成CPS:
let rec quicksort list accu cont =
match list with
| [] -> cont accu
| element::[] -> cont (element::accu)
| pivot::rest ->
let left, right = List.partition (fun element -> element < pivot) rest in
quicksort right accu (fun sorted_right ->
quicksort left (pivot :: sorted_right) cont)
let qsort li = quicksort li [] (fun x -> x)
現在最後關鍵是要通過把它們變成數據結構「defunctionalize」的延續(假設數據結構的分配略小於封閉的分配更有效):
type 'a cont =
| Left of 'a list * 'a * 'a cont
| Return
let rec quicksort list accu cont =
match list with
| [] -> eval_cont cont accu
| element::[] -> eval_cont cont (element::accu)
| pivot::rest ->
let left, right = List.partition (fun element -> element < pivot) rest in
quicksort right accu (Left (left, pivot, cont))
and eval_cont = function
| Left (left, pivot, cont) ->
(fun sorted_right -> quicksort left (pivot :: sorted_right) cont)
| Return -> (fun x -> x)
let qsort li = quicksort li [] Return
最後,我選擇了function .. fun
風格eval_cont
,使之明顯,這些只是從CPS版本的代碼段,但以下版本可能是更好的元數籌措優化:
and eval_cont cont accu = match cont with
| Left (left, pivot, cont) ->
quicksort left (pivot :: accu) cont
| Return -> accu
第二種情況不是嚴格必要的,因爲第三種情況會爲它做正確的事情。我不會將quicksort作爲尾部遞歸來對抗它的本質,而延續「優化」。你不會在堆棧中分配的所有東西都會堆在一堆,你必須意識到。 – 2011-04-12 11:00:55
這是沒有必要的,但因爲我不知道它將如何編譯,我認爲這會節省幾個函數調用。 (儘管由於O(1)運行時間而不是那麼重要) – 2011-04-12 11:03:23
我只是將普通版本稱爲未優化版本。我意識到可能會有更多的內存使用。堆棧中不存在一些限制,因此它可以幫助減少堆棧溢出。 – 2011-04-12 11:05:33