Recursion 带尾部递归的树到有序列表
事实上,我在一个问题上坐了一个多小时,却没有找到解决办法。 我有以下数据类型:Recursion 带尾部递归的树到有序列表,recursion,ocaml,tail-recursion,Recursion,Ocaml,Tail Recursion,事实上,我在一个问题上坐了一个多小时,却没有找到解决办法。 我有以下数据类型: 键入'a tree=Empty |'a*'树的节点*a树 我必须找到一个函数,它可以转换一个有序列表中的给定树。也没有像左子对象必须小于右子对象这样的不变量。我已经找到了一个“正常”的递归解决方案,但不是尾部递归解决方案。我已经考虑过构建一个无序列表,并使用list.sort对其进行排序,但这使用了一种不是尾部递归的合并排序。也许有人有个好建议 谢谢大家! 如果您希望按顺序遍历树并返回列表,这意味着我们的函数在ord
键入'a tree=Empty |'a*'树的节点*a树
我必须找到一个函数,它可以转换一个有序列表中的给定树。也没有像左子对象必须小于右子对象这样的不变量。我已经找到了一个“正常”的递归解决方案,但不是尾部递归解决方案。我已经考虑过构建一个无序列表,并使用list.sort
对其进行排序,但这使用了一种不是尾部递归的合并排序。也许有人有个好建议
谢谢大家! 如果您希望按顺序遍历树并返回列表,这意味着我们的函数
在order
中必须具有类型'a tree->'a list
let rec inorder t =
match t with
| Empty -> []
| Node (v, l, r) -> List.append (inorder l) (v :: (inorder r)) (* ! *)
但是,List.append
位于尾部位置,而不是inoder
。另一个问题是我们有两个调用的顺序
。如果我们把inorder l
放在尾部位置,inorder
不可能在尾部位置,反之亦然
解决这个问题的一个好方法是继续传球。我们使用上面的函数,并将其转换为一个辅助函数,该函数带有一个额外的参数,用于继续,return
(* convert to helper function, add an extra parameter *)
let rec loop t return =
match t with
| Empty -> ...
| Node (v, l, r) -> ...
continuation表示“下一步要做什么”,因此我们必须将值直接发送到continuation,而不是从函数中发送。这意味着对于Empty
案例,我们将返回[]
——而不是简单地[]
let rec loop t return =
match t with
| Empty -> return []
| Node (v, l, r) -> ...
对于节点(v,l,r)
的情况,现在我们有了一个额外的参数,我们可以编写自己的延续,通知循环
接下来要做什么。因此,为了构建我们的排序列表,我们需要循环l
,然后循环r
(反之亦然),然后我们可以附加它们。我们将这样编写程序
let rec loop t return =
match t with
| Empty -> return []
| Node (v, l, r) ->
loop l ... (* build the left_result *)
loop r ... (* build the right_result *)
return (List.append left_result (v :: right_result))
在下一步中,我们将填写continuations的实际lambda语法
let rec loop t return =
match t with
| Empty -> return []
| Node (v, l, r) ->
loop l (fun left ->
loop r (fun right ->
return (List.append left (v :: right))))
最后,我们定义了inorder
,它是对loop
的调用,带有默认的延续,identity
let identity x =
x
let inorder t =
let rec loop t return =
match t with
| Empty -> return []
| Node (v, l, r) ->
loop r (fun right ->
loop l (fun left ->
return (List.append left (v :: right))))
in
loop t identity
正如您所看到的,循环r(有趣的右->…)
位于节点的尾部位置<代码>循环l(左->…)
位于第一个延续的尾部位置。并且List.append…
位于第二个延续的尾部位置。提供的List.append
是一个尾部递归过程,inorder
不会增加堆栈
注意:对于大树来说,使用List.append
可能是一个代价高昂的选择。我们的函数为每个节点调用它一次。你能想出一个办法来避免它吗?这个练习留给读者。听起来像是一个作业。需要考虑的一点是,任何代码都可以使用“延续传递样式”转换为尾部递归代码。将代码转换为这种风格需要一个机械的过程。如果您阅读了有关转换的内容,您可能可以手动将其应用到代码中。这就是我要开始的地方。有趣的是:RosettaCode-OCaml哇,太棒了!没想到这么容易。我已经实现了一个非常“脏”的版本,有一个额外的堆栈来存储父母等等。但是这个版本非常好!