如何在不使用递归的情况下遍历树?


19

我的内存节点树很大,需要遍历该树。将每个子节点的返回值传递到其父节点。必须这样做,直到所有节点的数据都泡到根节点为止。

遍历是这样的。

private Data Execute(Node pNode)
{
    Data[] values = new Data[pNode.Children.Count];
    for(int i=0; i < pNode.Children.Count; i++)
    {
        values[i] = Execute(pNode.Children[i]);  // recursive
    }
    return pNode.Process(values);
}

public void Start(Node pRoot)
{
    Data result = Execute(pRoot);
}

这工作正常,但我担心调用堆栈会限制节点树的大小。

如何重写代码,以便不进行递归调用Execute


8
您要么必须维护自己的堆栈来跟踪节点,要么更改树的形状。参见stackoverflow.com/q/5496464stackoverflow.com/q/4581576
Robert Harvey

1
我在此Google搜索中也找到了很多帮助,特别是Morris Traversal
罗伯特·哈维

@RobertHarvey感谢Rob,我不确定这该用什么术语。
Reactgular 2014年

2
如果您进行了数学计算,您可能会对内存要求感到惊讶。例如,一个完美平衡的Teranode二叉树仅需要40个条目深的堆栈。
Karl Bielefeldt 2014年

@KarlBielefeldt假设树是完美平衡的。有时候,你必须塑造用于树木均衡,在这种情况下,它是容易吹堆栈。
2014年

Answers:


27

这是不使用递归的通用树遍历实现:

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

在您的情况下,您可以这样称呼它:

IEnumerable<Node> allNodes = Traverse(pRoot, node => node.Children);

首先使用a Queue代替Stack呼吸,而不是先进行深度搜索。使用以PriorityQueue获得最佳的首次搜索。


我是否认为这会将树弄平成一个集合是正确的?
Reactgular 2014年

1
@MathewFoscarini是的,这就是它的目的。当然,不一定需要将其具体化为实际的集合。这只是一个序列。您可以对其进行迭代以流式传输数据,而无需将整个数据集拉入内存。
2014年

我认为这不能解决问题。
Reactgular 2014年

4
他不仅遍历图形来执行诸如搜索之类的独立操作,而且还在汇总来自子节点的数据。展平树会破坏他执行聚合所需的结构信息。
Karl Bielefeldt 2014年

1
仅供参考,我认为这是大多数搜索此问题的人的正确答案。+1
Anders Arpi

4

如果您事先估计了树的深度,那么您的情况就足以适应堆栈大小了吗?在C#自2.0版以来,只要您启动新线程,就可以实现此功能,请参见此处:

http://www.atalasoft.com/cs/blogs/rickm/archive/2008/04/22/increasing-the-size-of-your-stack-net-memory-management-part-3.aspx

这样,您可以保留递归代码,而不必实现更复杂的东西。当然,使用自己的堆栈创建非递归解决方案可能会节省更多时间和内存,但是我敢肯定代码现在不会像现在这么简单。


我做了一个快速测试。在我的机器上,我可以在达到stackoverflow之前进行14000次递归调用。如果树是平衡的,则只需要32个调用即可存储40亿个节点。如果每个节点为1个字节(它不会是)这将需要的RAM 4 GB存储高度32的平衡树
Esben Skov的彼得森

我要使用堆栈中的所有14000个调用。如果每个节点都是一个字节(不会),则树将占用2.6x10 ^ 4214字节
Esben Skov Pedersen

-3

不使用递归就无法遍历树形数据结构-如果不使用语言提供的堆栈框架和函数调用,则基本上必须编写自己的堆栈和函数调用,不太可能,你能做到这中的语言更有效的方式方法比编译器作者的机器,你的程序将运行上做到了。

因此,通常避免误导,以免担心会遇到资源限制。可以肯定的是,过早的资源优化总是被误导,但是在这种情况下,即使您衡量并确认内存使用量是瓶颈,也可能无法在不降低性能的情况下对其进行改进。编译器作者。


2
这是完全错误的。当然,不使用递归就可以遍历树。甚至都不难。您还可以非常高效地做到这一点,因为您可以确保在特定遍历中只需要包含尽可能多的信息,而在递归中最终存储的信息要比许多实际需要的信息多案件。
2014年

2
在这里每隔一段时间就会引起争议。一些发布者认为滚动自己的堆栈不是递归,而其他发布者则指出它们只是在做相同的事情,否则运行时将隐式地做。争论这样的定义毫无意义。
Kilian Foth,2014年

那么您如何定义递归?我将其定义为在自己的定义内调用自身的函数。正如我在回答中所展示的,您绝对可以不经过那棵树。
2014年

2
我喜欢享受点击声望很高的某人的降级投票的行为吗?这个网站上真是一种难得的乐趣。
Reactgular 2014年

2
来@Mat,那是孩子们的东西。您可能会不同意,例如,如果您担心在一棵过深的树上被炸开,那是一个合理的问题。你可以这么说。
Mike Dunlavey 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.