c# - 如何在C#中折叠一棵n叉树

标签 c# f# tree fold catamorphism

我想对 n 元树数据结构进行折叠。 (折叠在 Linq 中又称为聚合)
我设法想出了一个可行的解决方案:

public static R Aggregate<T, R>(T node,
          Func<T, IEnumerable<T>> getChildren,
       Func<T, IEnumerable<R>, R> aggregator)
{
    var childResults = getChildren(node)
                      .Select(c => Aggregate(c, getChildren, aggregator));

    return aggregator(node, childResults);
}

getChildren 是定义如何获取给定节点的子节点的函数。它必须为叶节点返回一个空的 IEnumerable。
聚合器 定义了如何使用当前节点及其子节点的结果来处理节点。

该解决方案似乎有效但存在一些问题:

  • 算法是递归的,它会炸毁深树的堆栈。
    如何重写函数以防止堆栈溢出?

  • 算法是惰性的,但只是有点。
    例如如果聚合器只使用子节点的Enumerable.First结果,则只遍历树的最左边的分支。然而,对于 Enumerable.Last,整个树都会被遍历,即使计算只需要最右边的分支。
    如何让算法真正懒惰?

欢迎使用 F# 解决方案,但首选 C#。

最佳答案

您可以使用显式堆栈而不是递归来遍历树,以避免消耗堆栈空间:

public static IEnumerable<T> Traverse<T>(
    this IEnumerable<T> source
    , Func<T, IEnumerable<T>> childrenSelector)
{
    var stack = new Stack<T>(source);
    while (stack.Any())
    {
        var next = stack.Pop();
        yield return next;
        foreach (var child in childrenSelector(next))
            stack.Push(child);
    }
}

如果你想“向后”遍历,你可以在调用它时简单地调整子选择器,而不是调用 Last 而不是 First:

Traverse(root, node => nodes.Reverse());

关于c# - 如何在C#中折叠一棵n叉树,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19543939/

相关文章:

c# - request.GetResponse 总是给出超时

c# - 无法将 System.Web.UI.WebControls.Label 类型的对象转换为类型 System.IConvertible

visual-studio - 我可以在 Visual Studio Express 2012 for Windows Desktop 中使用 F# 吗?

algorithm - 非递归 n 射线树遍历

java - 哈夫曼树算法难以理解

c# - 在引用多目标类库时,如何让 NuGet 知道包的特定版本?

C#模板参数作为模板接口(interface)

c# - 默认情况下,F# 中的引用是否不可为空?

mysql - F# 错误 'error FS0039: The namespace or module ' MySql'未定义'?

javascript - 递归javascript函数返回数组