直接的风格:
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