我有二元树和深度函数的以下实现来计算它的深度:
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循环呢?
答案 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件事:
每当我们遇到需要消除可能的堆栈溢出问题的问题时,我们应该考虑两件事:尾递归和显式堆栈。
对于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