【问题标题】:Tree to ordered list with tail recursion树到带有尾递归的有序列表
【发布时间】:2018-12-15 19:44:13
【问题描述】:

实际上,我在一个问题上坐了一个多小时,却没有找到解决方案。 我有这个数据类型: type 'a tree = Empty | Node of 'a * 'a tree * 'a tree

而且我必须找到一个函数来转换有序列表中的给定树。也没有像左孩子必须小于右孩子这样的不变量。我已经找到了“正常”递归解决方案,但没有找到尾递归解决方案。我已经考虑过构建一个无序列表并使用List.sort 对其进行排序,但这使用了不是尾递归的合并排序。也许有人有好的建议。

谢谢!

【问题讨论】:

  • 这听起来像是一项任务。需要考虑的一件事是,任何代码都可以使用“继续传递样式”转换为尾递归代码。将代码转换为这种风格有一个机械过程。如果您阅读了有关转换的信息,您可能可以手动将其应用到您的代码中。这就是我要开始的地方。
  • 感兴趣的:RosettaCode - OCaml Tree traersal

标签: recursion ocaml tail-recursion


【解决方案1】:

如果您想按顺序遍历树并返回一个列表,这意味着我们的函数inorder必须具有'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 在尾部位置,而不是inorder。另一个问题是我们有两次调用inorder。如果我们将inorder l 放在尾部位置,inorder r 就不可能处于尾部位置 - 反之亦然。

解决此问题的一种巧妙方法是延续传递样式。我们将上面的函数转换为一个辅助函数,并为我们的继续添加一个额外的参数,return

(* convert to helper function, add an extra parameter *)
let rec loop t return =
    match t with
        | Empty -> ...
        | Node (v, l, r) -> ...

延续代表“下一步该做什么”,因此我们不能直接从我们的函数中发送值,而是必须将它们交给延续。这意味着对于Empty 案例,我们将return [] - 而不是简单的[]

let rec loop t return =
    match t with
        | Empty -> return []
        | Node (v, l, r) -> ...

对于Node (v, l, r) 的情况,现在我们有了一个额外的参数,我们可以编写自己的延续来通知loop 下一步该做什么。所以要构造我们的排序列表,我们需要loop l,然后是loop r(反之亦然),然后我们可以append他们。我们将像这样编写我们的程序。

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

在下一步中,我们将为延续填写实际的 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

如您所见,loop r (fun right -> ...) 位于 Node 分支的尾部。 loop l (fun left -> ...) 位于第一个延续的尾部位置。而List.append ... 在第二个延续的尾部位置。如果List.append 是一个尾递归过程,inorder 将不会增长堆栈。


注意使用List.append 对于大树来说可能是一个代价高昂的选择。我们的函数每Node 调用一次。你能想出避免它的方法吗?这个练习留给读者。

【讨论】:

  • 哇,太好了!没想到这么容易。我已经实现了一个非常“脏”的版本,带有一个额外的堆栈来存储父母等。但是这个非常好!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-05-25
  • 2017-03-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多