First off, you are absolutely correct; if the graph has n nodes of average depth d then the naive nested iterators yield a solution which is O(n*d) in time, and O(d) in stack. If d is a large fraction of n then this can become an O(n2) algorithm, and if d is large then you can blow the stack entirely.
如果您对嵌套迭代器的性能分析感兴趣,请参阅前 C# 编译器开发人员 Wes Dyer 的博客文章:
http://blogs.msdn.microsoft.com/wesdyer/2007/03/23/all-about-iterators http://blogs.msdn.microsoft.com/wesdyer/2007/03/23/all-about-iterators
dasblinkenlight 的解决方案是标准方法的变体。我通常会这样编写程序:
public static IEnumerable<T> Traverse<T>(
T root,
Func<T, IEnumerable<T>> children)
{
var stack = new Stack<T>();
stack.Push(root);
while(stack.Count != 0)
{
T item = stack.Pop();
yield return item;
foreach(var child in children(item))
stack.Push(child);
}
}
然后如果你有多个根:
public static IEnumerable<T> Traverse<T>(
IEnumerable<T> roots,
Func<T, IEnumerable<T>> children)
{
return from root in roots
from item in Traverse(root, children)
select item ;
}
现在,请注意,遍历是not如果您有一个高度互连的图或循环图,这就是您想要的!如果您有一个带有向下箭头的图表:
A
/ \
B-->C
\ /
D
那么遍历是A,B,D,C,D,C,D。如果你有一个循环图或互连图,那么你想要的是传递闭包.
public static IEnumerable<T> Closure<T>(
T root,
Func<T, IEnumerable<T>> children)
{
var seen = new HashSet<T>();
var stack = new Stack<T>();
stack.Push(root);
while(stack.Count != 0)
{
T item = stack.Pop();
if (seen.Contains(item))
continue;
seen.Add(item);
yield return item;
foreach(var child in children(item))
stack.Push(child);
}
}
此变化仅产生之前未产生过的物品。
我也不太擅长消除递归。
我写了很多关于消除递归的方法以及一般的递归编程的文章。如果您对这个主题感兴趣,请参阅:
http://blogs.msdn.com/b/ericlippert/archive/tags/recursion/ http://blogs.msdn.com/b/ericlippert/archive/tags/recursion/
尤其:
http://blogs.msdn.com/b/ericlippert/archive/2005/08/01/recursion-part-two-unrolling-a-recursive-function-with-an-explicit-stack.aspx http://blogs.msdn.com/b/ericlippert/archive/2005/08/01/recursion-part-two-unrolling-a-recursive-function-with-an-explicit-stack.aspx
http://blogs.msdn.com/b/ericlippert/archive/2005/08/04/recursion-part- Three-building-a-dispatch-engine.aspx http://blogs.msdn.com/b/ericlippert/archive/2005/08/04/recursion-part-three-building-a-dispatch-engine.aspx
http://blogs.msdn.com/b/ericlippert/archive/2005/08/08/recursion-part-four-continuation-passing-style.aspx http://blogs.msdn.com/b/ericlippert/archive/2005/08/08/recursion-part-four-continuation-passing-style.aspx