【问题标题】:Counting nodes in a Tree计算树中的节点
【发布时间】:2013-02-12 14:01:37
【问题描述】:

fighting with f# - 战斗在 Trees 领域 - 专门计算节点的数量。这真的很有趣,因为我想最终用 F# 编写的程序涉及多路树,不幸的是它的开始有点麻烦 - 我希望你能提供帮助!

99 f# 系列的第 61 题,要求计算二叉树的叶子。解决方案(如下所示)计算节点,但我的问题不理解

  • 双递归的工作原理向左循环(有趣的 lacc -> 向右循环..)

  • cont (branchF x lacc racc) 是什么,我的印象是 cont 是“abc”函数,但这只需要两个参数...

  • loop t id id 的类型为 unit - 我不明白这是如何暗示的

基本上不理解这一点,或者它在树中流动的顺序(调试和单步执行没有帮助)如果有更简单的示例、预读建议等,请指导我。

非常感谢您的帮助,有问题的解决方案代码如下:

干杯

td

type 'a Tree = Empty | Branch of 'a * 'a Tree * 'a Tree

let foldTree branchF emptyV t =
    let rec loop t cont =
        match t with
        | Empty ->
            cont emptyV
        | Branch (x, left, right) ->
            loop left  (fun lacc -> 
                loop right (fun racc ->
                    cont (branchF x lacc racc)))
    loop t id

let counLeaves tree =
    foldTree (fun abc lc rc ->
        if lc + rc = 0 then 1
        else 1 + lc + rc) 0 tree

let Tree1 = Branch ('x', Branch ('x', Empty, Empty),Branch ('x', Empty, Branch ('x', Empty, Branch ('x', Empty, Empty))))


let result = counLeaves Tree1

【问题讨论】:

  • 我不知道我在写解决方案时在想什么,但是 Lee 是 wright 并且 counLeaves 中的“if”没有任何作用。解决方案应该是 let counLeaves tree = tree |> foldTree (fun _ lc rc -> 1 + lc + rc) 0。如果你想了解更多关于折叠和延续的信息。我推荐这里link 的 Brian McNamara 的变形论系列。这就是我得到 fodlTree 函数的地方。
  • 嗨,Cesar,我只想说感谢您发布 99 个问题系列 - 非常棒的学习工具 - 特别是多种解决方案。干杯!
  • 鉴于没有人回答您关于 id 的问题,“id”是身份运算符。您可以将 id 替换为 (fun x -> x) 以获得相同的效果。见这里:msdn.microsoft.com/en-us/library/ee353607.aspx 另外,这段代码有一些非常错误的地方——我创建了一个简单的 4 节点树,它告诉我大小是 32!一旦弄清楚所有细微差别,我将发布正确的实现(我正在工作)。

标签: recursion f# tree continuations


【解决方案1】:

顾名思义,foldTree 定义了自定义 Tree 类型之上的折叠函数。

定义foldTree 的简单方法可能是:

let rec foldTreeNaive accFun init = function
    | Empty -> init
    | Branch (x, left, right) ->
        let lacc = foldTreeNaive accFun init left
        let racc = foldTreeNaive accFun init right
        accFun x lacc racc

这个函数的问题是,如果被折叠的树很深,它可能会进行非常深的递归调用,因为在调用累加器函数之前必须完成节点的递归调用。例如以下导致堆栈溢出异常:

let unbalanced = [1..100000] |> List.fold (fun t i -> Branch(i, t, Empty)) Empty
let nodeCount = foldTreeNaive (fun _ lc rc -> lc + rc + 1) 0 unbalanced

避免这种堆栈溢出的常用方法是使函数尾部递归,但是在这种情况下这似乎是不可能的,因为要进行两次递归调用,而不是折叠列表时需要的一次。

foldTree 是使用本地 loop 函数定义的。这个函数很有趣,因为它是使用continuation passing style 定义的。在 CPS 中,每个函数都有一个附加的“延续”函数,该函数传递计算结果并负责决定接下来会发生什么。注意loop是尾递归的,因此避免了foldTreeNaive的溢出问题。

loop函数的类型是:

Tree<'a> -> ('b -> 'c) -> 'c

其中'a是树中节点的类型,'b是累加器类型,'c是延续函数的结果。

在叶节点的情况下,将传递给foldTree 函数的空累加器值传递给延续。

Branch 情况下折叠非空树时,折叠的结果取决于左右子树的结果。这是递归完成的,首先是折叠左子树,然后是右子树。对于左子树的递归调用,loop 必须建立一个新的延续来接收结果,这是

(fun lacc -> 
    loop right (fun racc ->
        cont (branchF x lacc racc))

功能。这个延续所做的是对右子树进行递归调用,传递另一个延续来接收折叠的结果。当调用该延续时,左子树和右子树的结果在laccracc 中可用。此时,可以使用当前节点的值和左右子树的结果调用节点的累积函数。然后将这个函数的结果传递给传递给loop 的原始延续。

loop 函数随后由该行中的foldTree 函数调用:

loop t id

这里,id 是接收树根节点折叠结果的延续。由于这是所需的值,id 只返回其参数而无需修改。

您可能还会发现this description of fold for binary trees 很有用。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-13
    • 1970-01-01
    相关资源
    最近更新 更多