gpt4 book ai didi

f# - 在 F#/OCaml 中实现类似快速排序的函数的尾递归版本

转载 作者:行者123 更新时间:2023-12-04 16:27:59 24 4
gpt4 key购买 nike

是否可以实现快速排序算法的尾递归版本(通过延续模式)?如果是的话,人们将如何实现它?

普通(未优化)版本:

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``

最佳答案

直接风格:

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 的版本非常相似,除了缩进有点奇怪以表明带有延续的调用实际上是一种绑定(bind):
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 得到更好的优化:
and eval_cont cont accu = match cont with
| Left (left, pivot, cont) ->
quicksort left (pivot :: accu) cont
| Return -> accu

关于f# - 在 F#/OCaml 中实现类似快速排序的函数的尾递归版本,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5634083/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com