Recursion 没有堆栈溢出的OCaml二叉树深度

Recursion 没有堆栈溢出的OCaml二叉树深度,recursion,ocaml,binary-tree,tail-recursion,Recursion,Ocaml,Binary Tree,Tail Recursion,我有一个二叉树的以下实现和一个计算深度的深度函数: type 'a btree = | Empty | Node of 'a * 'a btree * 'a btree;; let rec depth t = match t with | Empty -> 0 | Node (_, t1, t2) -> 1 + Int.max (depth t1) (depth t2) 这里的问题是“深度”是递归的,当树太大时会导致堆栈溢出 我读过关于尾部递归的内容,以及编译器如何将尾部递归优

我有一个二叉树的以下实现和一个计算深度的深度函数:

type 'a btree =
| Empty
| Node of 'a * 'a btree * 'a btree;;

let rec depth t = match t with
| Empty -> 0
| Node (_, t1, t2) -> 1 + Int.max (depth t1)  (depth t2)
这里的问题是“深度”是递归的,当树太大时会导致堆栈溢出

我读过关于尾部递归的内容,以及编译器如何将尾部递归优化为while循环以删除堆栈调用

您将如何使这个函数尾部递归,或者使它改用while/for循环

type 'a btree =
| Empty
| Node of 'a * 'a btree * 'a btree;;

let max x y = if x > y then x else y

let depth t =
  let rec dep m = function (* d records current level, m records max depth so far *)
    | [] -> m
    | (Empty,d)::tl -> dep (max m d) tl
    | (Node (_,l,r),d)::tl -> dep (max m d) ((l,d+1)::(r,d+1)::tl)
  in 
  dep 0 [(t,0)]
基本上,你需要三件事:

  • 用于沿路径存储节点的列表(堆栈)
  • 记录当前深度的指示器
  • 目前为止的最大深度
  • 每当我们遇到需要消除可能的堆栈溢出问题的问题时,我们应该考虑两件事:尾部递归显式堆栈

    对于尾部递归,您必须找到一种方法来显式存储通过每个递归步骤生成的临时数据


    对于显式堆栈,请记住递归可以工作的原因是因为它在内部使用大小有限的堆栈。如果我们分析逻辑并使堆栈显式,我们就不再需要内部堆栈。

    在实际情况下,解决方案是使用平衡树,它将深度限制为log(n)的某个倍数。即使对于非常大的n,log(n)也足够小,不会耗尽堆栈空间


    否则,请参见Kadaku链接的SO页面。它对这个问题的回答非常好。

    我已经回答过一次类似的问题。重新发布解决方案:

    使用
    折叠树和CPS-连续传球风格,有一个简洁而通用的解决方案:

    let fold_tree tree f acc =
      let loop t cont =
        match tree with
        | Leaf -> cont acc
        | Node (x, left, right) ->
          loop left (fun lacc ->
            loop right (fun racc ->
              cont @@ f x lacc racc))
      in loop tree (fun x -> x)
    
    let depth tree = fold_tree tree (fun x dl dr -> 1 + (max dl dr)) 0
    

    将其转换为尾部递归或迭代算法需要使用显式堆栈。这是一个真正的问题还是一个练习?因为在实际应用程序中,您要么确保树是平衡的,这样递归就不会太深,要么在构建节点时将深度存储在节点中。感谢您为实际应用程序提供的提示。不过,这是一个练习。可能的dublicate:嗯。。似乎它不起作用:给定:Node(10,Node(5,空,空),Node(15,空,空))返回7这很聪明(至少对我来说),但我必须重读几遍才能理解它。谢谢!另一个提示是ocaml/FP非常方便地在临时基础上将信息组合在一起。和我一样,记录每个节点的级别信息
    d
    。@Jackson\u因此,显式堆栈基本上就像一个临时存储器,用于存储需要处理的节点,一旦处理一个节点,就将其删除并将其子项添加到堆栈中,然后当到达一个空的(叶)时您只需删除,这就是您最终得到空堆栈[]的方式,您只需返回到目前为止的当前最大值(m)。是吗?哦,我现在明白了,你基本上检查,移除,添加孩子。当循环碰到一片叶子时,它最终停止添加更多。每次你点击一个节点时,你会检查它的等级是否高于当前的最大值,并更新最大值。最终你会返回最终的最大值