如何在F#中计算二叉树中的非空节点数

时间:2018-09-06 01:37:26

标签: f# binary-tree

考虑二叉树代数数据类型

type btree = Empty | Node of btree * int * btree

和定义如下的新数据类型:

 type finding = NotFound | Found of int

到目前为止,这里有我的代码:

let s = Node (Node(Empty, 5, Node(Empty, 2, Empty)), 3, Node (Empty, 6, Empty))
(*
     (3)
    /    \
   (5)   (6)
   / \   |  \
  () (2) () ()
 / \
() ()
*)

(* size: btree -> int *)
let rec size t =
    match t with
    Empty -> false
  | Node (t1, m, t2) -> if (m != Empty) then sum+1 || (size t1) || (size t2)

let num = occurs s
printfn "There are %i nodes in the tree" num

这可能还没有结束,我采用了一个函数,该函数将查找树中是否存在整数,然后尝试更改我尝试执行的代码。

我对使用F#非常陌生,不胜感激。我试图计算树中所有非空节点。例如,我正在使用的树应打印值4。

2 个答案:

答案 0 :(得分:3)

我没有在您的代码上运行编译器,但是我相信它甚至可以编译。 但是,您的想法是在递归函数中使用模式匹配。 正如rmunn所评论的,您想确定每种情况下的节点数: 空树没有节点,因此结果为零。 一个非空树,至少具有根节点加上其左右子树的数量。

因此,应遵循以下内容

let rec size t =
    match t with
    | Empty -> 0
    | Node (t1, _, t2) -> 1 + (size t1) + (size t2)

这里最重要的细节是,您不需要全局变量sum来存储任何中间值。递归函数的整个想法是,这些中间值是递归调用的结果。

我想指出,您的评论中的树应该看起来像这样。

(*
     (3)
    /    \
   (5)   (6)
   / \   |  \
  () (2) () ()
     / \
    () ()
*)

编辑:我将未对齐的()误读为一棵空树的叶子,实际上它们是子树(2)的叶子。所以这只是一个ASCII艺术问题:-)

答案 1 :(得分:2)

Friedrich已经发布了size函数的简单版本,该函数适用于大多数树。但是,该解决方案不是“尾递归”的,因此它可能导致大树的堆栈溢出。在像F#这样的函数式编程语言中,对于诸如计数和其他聚合函数之类的东西,递归通常是首选的技术。但是,递归函数通常会为每个递归调用占用一个堆栈帧。这意味着对于大型结构,可以在函数完成之前耗尽调用堆栈。为了避免此问题,编译器可以优化被认为是“尾递归”的函数,以便它们仅使用一个堆栈帧,而不管它们要递归多少次。不幸的是,这种优化不能仅仅针对任何递归算法来实现。它要求递归调用是函数要做的最后一件事,从而确保编译器不必担心调用后跳回到函数中,从而允许其覆盖堆栈框架而不用添加另一个框架。

为了将size函数更改为尾递归,我们需要某种方法来避免在非空节点的情况下必须两次调用它,以便将调用作为最后一步功能,而不是在Friedrich解决方案的两个调用之间添加。这可以使用几种不同的技术来完成,通常使用累加器或使用Continuation Passing Style。比较简单的解决方案通常是使用累加器来跟踪总大小而不是将其作为返回值,而“连续传递样式”是一种更通用的解决方案,可以处理更复杂的递归算法。

为了使累加器模式可用于必须对左右子树求和的树,我们需要某种方法在函数末尾进行一次尾调用,同时还要确保两个子树都被评估。一种简单的方法是除总数之外还累加正确的子树,因此我们可以进行后续的尾部调用来评估那些树,同时先评估左边的子树。该解决方案可能看起来像这样:

let size t =
    let rec size acc ts = function
    | Empty -> 
        match ts with
        | [] -> acc
        | head :: tail -> head |> size acc tail
    | Node (t1, _, t2) -> 
        t1 |> size (acc + 1) (t2 :: ts)

    t |> size 0 []

这将添加acc参数和ts参数,以表示总数和剩余的未评估子树。当我们点击一​​个填充的节点时,我们评估左侧的子树,同时将右侧的子树添加到我们的树列表中以供以后评估。当我们碰到一个空节点时,我们将开始评估积累的任何ts,直到没有其他填充的节点或未评估的子树为止。这不是计算树大小的最佳解决方案,大多数真正的解决方案将使用Continuation Passing Style使其恢复尾音,但是随着您对语言的熟悉,这应该是一个很好的练习。