我正在试图找出如何在树节点中实现一个函数,该节点返回其所有后代叶子(无论是直接还是间接)。但是,我不想传递一个容器,其中叶子节点将递归放置(树可能很大),而是我想使用生成器迭代树。我尝试了一些方法,但到目前为止还没有一个方法。这是我最接近可能的解决方案:
public interface ITreeNode
{
IEnumerable<ITreeNode> EnumerateLeaves();
}
class Leaf : ITreeNode
{
public IEnumerable<ITreeNode> EnumerateLeaves()
{
throw new NotImplementedException();
}
}
class Branch : ITreeNode
{
private List<ITreeNode> m_treeNodes = new List<ITreeNode>();
public IEnumerable<ITreeNode> EnumerateLeaves()
{
foreach( var node in m_treeNodes )
{
if( node is Leaf )
yield return node;
else
node.EnumerateLeaves();
}
}
}
但这也不起作用。我究竟做错了什么?如果在同一个函数中有一个yield语句,似乎递归地调用.EnumerateLeaves将不起作用。
非常感谢任何帮助。提前谢谢。
编辑:我忘了提到一个分支可以有叶子或分支作为子项,因此递归。
答案 0 :(得分:7)
以下是如何实现Branch.EnumerateLeaves:
public IEnumerable<ITreeNode> EnumerateLeaves()
{
foreach( var node in m_treeNodes )
{
if( node is Leaf )
yield return node;
else
{
foreach (ITreeNode childNode in node.EnumerateLeaves())
yield return childNode;
}
}
}
答案 1 :(得分:3)
lassevk是对的 - 然而,该方法的一个潜在问题是递归调用枚举可以产生O(n ^ 2)性能。如果这是一个问题,那么你应该考虑递归并使用内部堆栈。
public IEnumerable<ITreeNode> EnumerateLeaves()
{
Stack<ITreeNode> workStack = new Stack<ITreeNode>(m_treeNodes);
while(workStack.Count > 0) {
var current = workStack.Pop();
if(current is Leaf)
yield return current;
else {
for(n = 0; n < current.m_treeNodes.Count; n++) {
workStack.Push(current.m_treeNodes[n]);
}
}
}
}
这应该执行相同的功能,没有递归。
编辑:Daniel Plaisted在递归调用枚举数的评论中发布,总结在post on the MSDN Blogs regarding iterators。谢谢Daniel。 =)
另一个编辑:始终记住代码简单性比性能更重要,特别是如果您希望其他人维护您的代码。如果你不希望你的树长得很大,请使用他的答案中概述的递归方法lassevk。
答案 2 :(得分:1)
其他答案是正确的,但是使用递归调用在foreach循环内返回yield的模式将使运行时间迭代遍历树,如O(节点数*节点的平均深度)。有关该问题的详细信息,请参阅this blog post。
这是对生成器的尝试,它在运行时和内存使用方面都很有效:
class Node
{
List<Node> _children;
public bool IsLeaf { get { return _children.Count == 0; } }
public IEnumerable<Node> Children { get { return _children; } }
public IEnumerable<Node> EnumerateLeaves()
{
if (IsLeaf)
{
yield return this;
yield break;
}
var path = new Stack<IEnumerator<Node>>();
path.Push(Children.GetEnumerator());
while(!path.Empty)
{
var cur = path.Pop();
if (cur.MoveNext())
{
path.Push(cur);
if (cur.IsLeaf)
{
yield return cur;
}
else
{
path.Push(cur.Children.GetEnumerator());
}
}
}
}
}