F# 如何计算F中二叉树中的非空节点数#
考虑二叉树代数数据类型F# 如何计算F中二叉树中的非空节点数#,f#,binary-tree,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) / \
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 我没有在您的代码上运行编译器,但我相信它甚至可以编译。 然而,在递归函数中使用模式匹配的想法是好的。 正如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艺术问题:-)弗里德里希已经发布了一个简单版本的size
函数,该函数适用于大多数树。但是,该解决方案不是“尾部递归”,因此它可能会导致大型树的堆栈溢出。在函数式编程语言(如F#)中,递归通常是计数和其他聚合函数的首选技术。但是,递归函数通常为每个递归调用使用一个堆栈帧。这意味着对于大型结构,可以在函数完成之前耗尽调用堆栈。为了避免这个问题,编译器可以优化被认为是“尾部递归”的函数,这样无论递归多少次,它们都只使用一个堆栈帧。不幸的是,这种优化不能只针对任何递归算法实现。它要求递归调用是函数做的最后一件事,从而确保编译器不必担心调用后跳回函数,从而允许它覆盖堆栈帧而不是添加另一个堆栈帧
为了将size
函数更改为尾部递归函数,我们需要某种方法避免在非空节点的情况下调用它两次,以便调用可以是函数的最后一步,而不是Friedrich解决方案中两次调用之间的相加。这可以通过使用两种不同的技术来实现,通常可以使用累加器或连续传球方式。更简单的解决方案通常是使用累加器来跟踪总大小,而不是将其作为返回值,而延续传递样式是一种更通用的解决方案,可以处理更复杂的递归算法
为了使累加器模式适用于必须对左、右子树求和的树,我们需要某种方法在函数末尾进行一次尾部调用,同时仍然确保对两个子树进行求值。一种简单的方法是,除了总计数之外,还要累加右子树,这样我们就可以在先计算左子树的同时进行后续尾部调用来计算这些树。该解决方案可能如下所示:
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
,直到我们没有进一步填充的节点或未计算的子树。这不是计算树大小的最佳解决方案,大多数实际解决方案都会使用延续传递样式使其尾部重复,但随着您对该语言的熟悉,这应该是一个很好的练习。您可能还应该向我们展示您的btree
类型定义,因此,我们可以确切地看到节点
案例是如何定义的。这将有助于任何试图回答问题的人。我还建议阅读我给出的答案,并考虑我建议的一般方法:写下btree
数据类型的每个可能的案例,并决定每个案例应该返回什么值。例如,对于一个仅仅是空节点的树,您希望返回0,因为该树中没有非空节点。对于具有节点的树,您需要(左分支中的非空节点)、0或1(取决于根是否为空)和(右分支中的非空节点)之和。从这里开始。我添加了我要使用的数据类型,thanksI会注意到这个解决方案不是尾部递归的,因此它可能会使大型树的堆栈溢出。我同意Aaron所说的一切。只是为了让OP更直观:两种解决方案都执行树的预排序遍历。这些实现实际上只是在性能上有所不同——这里是函数式编程的一个警告。在某些情况下,如本例所示,试图优化递归函数,最终编写的代码类似于命令式程序。查看中的伪代码,您将看到Aaron的代码使用堆栈模拟while循环。这不是对解决方案的批评,只是提醒您“没有免费的午餐”;-)