是否可以实现快速排序算法的尾递归版本(通过延续模式)?如果是的话,如何实现呢?
正常(未优化)版本:
let rec quicksort list =
match list with
| [] -> []
| element::[] -> [element]
| pivot::rest -> let ``elements smaller than pivot``, ``elements larger or equal to pivot``=
rest |> List.partition(fun element -> element < pivot)
quicksort ``elements smaller than pivot`` @ [pivot] @ quicksort ``elements larger or equal to pivot``
答案 0 :(得分:13)
直接风格:
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函数具有线性使用延续的属性,在每个分支中只有一次,尾部位置。很容易检查没有忘记这样的电话。
但实际上,对于大多数快速排序运行(假设你得到一个粗略的对数行为,因为你并不是运气不好或者你先输入了输入),调用堆栈不是问题,因为它只是以对数方式增长。更令人担忧的是,@
频繁调用其左参数是线性的。一种常见的优化技术是将函数定义为不返回列表,而是将“输入添加到累加器列表”:
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)
现在最后一个技巧是通过将延续转换为数据结构来“延迟”延续(假设数据结构的分配比闭包的分配稍微有效):
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版本中的代码片段,但以下版本可能通过arity-raising更好地优化:< / p>
and eval_cont cont accu = match cont with
| Left (left, pivot, cont) ->
quicksort left (pivot :: accu) cont
| Return -> accu
答案 1 :(得分:3)
快速尝试,寻求工作:
let rec quicksort list cont =
match list with
| [] -> cont []
| element::[] -> cont [element]
| pivot::rest ->
let ``elements smaller than pivot``, ``elements larger or equal to pivot`` =
rest |> List.partition (fun element -> element < pivot)
quicksort ``elements smaller than pivot``
(fun x -> quicksort ``elements larger or equal to pivot`` (fun y -> cont (x @ [pivot] @ y)))
> quicksort [2; 6; 3; 8; 5; 1; 9; 4] id;;
val it : int list = [1; 2; 3; 4; 5; 6; 8; 9]
修改强>
当然,这段代码非常低效。我希望没有人会在实际代码中使用它。
代码并不难编写,但延续可能难以阅读并且容易出错(很容易忘记对cont
的调用)。如果你想玩更多,你可以写一个延续monad(Brian写a blog post about it)。
答案 2 :(得分:3)
也可以使用Continuation monad(stolen from here)(通常使代码更具可读性):
type ContinuationMonad() =
// ma -> (a -> mb) -> mb
member this.Bind (m, f) = fun c -> m (fun a -> f a c)
// a -> ma
member this.Return x = fun k -> k x
// ma -> ma
member this.ReturnFrom m = m
let cont = ContinuationMonad()
// Monadic definition of QuickSort
// it's shame F# doesn't allow us to use generic monad code
// (we need to use 'cont' monad here)
// otherwise we could run the same code as Identity monad, for instance
// producing direct (non-cont) behavior
let rec qsm = function
|[] -> cont.Return []
|x::xs -> cont {
let l,r = List.partition ((>=)x) xs
let! ls = qsm l
let! rs = qsm r
return (ls @ x :: rs) }
// Here we run our cont with id
let qs xs = qsm xs id
printf "%A" (qs [2;6;3;8;5;1;9;4])