OCaml二进制树深度,没有堆栈溢出

时间:2014-01-17 10:37:56

标签: 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)

这里的问题是“深度”是递归的,当树太大时会导致堆栈溢出。

我读到了关于尾递归以及它如何被编译器优化为while循环以删除堆栈调用。

你如何使这个函数尾递归或者使用while / for循环呢?

3 个答案:

答案 0 :(得分:3)

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

基本上,你需要3件事:

  1. 沿路径存储节点的列表(堆栈)
  2. 记录当前深度的指标
  3. 目前的最大深度
  4. 每当我们遇到需要消除可能的堆栈溢出问题的问题时,我们应该考虑两件事:尾递归显式堆栈

    对于tail-recursive,你必须找到一种方法来显式存储通过每个递归步骤生成的临时数据。

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

答案 1 :(得分:0)

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

否则请参阅由Kadaku链接的SO页面。它对这个问题的答案非常好。

答案 2 :(得分:0)

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

使用fold_tree和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