递归枚举图

时间:2012-02-05 18:52:47

标签: c# .net recursion enumeration

我有以下(其混凝土):

public interface IDirectory
{
   IEnumerable<IDirectory> Directories{get;}
}

我深入构建了一个图形 n 目录。现在我想枚举从根开始并递归到终端/最深目录的目录,我必须按顺序进行,I.E。在返回目录之前永远不会返回目录。我确信有一个优雅的解决方案,但它在我的时区已经很晚了,我的灰质已经累了,所以任何指针都非常赞赏。

3 个答案:

答案 0 :(得分:9)

另外两个解决方案是这个问题的典型递归解决方案,适用于没有周期的浅图。如果你有一个带有周期的深图,那么它们就不会有效。你最好用这样的东西:

public static IEnumerable<T> DepthFirstTraversal<T>(
    T root, 
    Func<T, IEnumerable<T>> children)
{
    var set = new HashSet<T>();
    var stack = new Stack<T>();
    stack.Push(root);
    while(stack.Count != 0)
    {
        T current = stack.Pop();
        if (set.Contains(current)) continue;
        yield return current;
        set.Add(current);
        foreach(var child in children(current))
            stack.Push(child);
    }
}

然后用

调用它
foreach(var d in DepthFirstTraversal(root, x=>x.Directories))
   ...

该集合跟踪已经访问过的节点,因此占用了O(n)空间。没有递归,所以它在堆栈空间中是O(1)。

如果您知道没有周期,则可以消除该设置并节省该费用。

答案 1 :(得分:4)

IEnumerable<IDirectory> Enumerate(IDirectory parent)
{
    yield return parent;
    foreach (var child in parent.Directories)
        foreach (var directory in Enumerate(child))
            yield return directory;
}

不是最有效的,但它有效。 Eric Lippert撰写了一些关于如何使用Stack<IDirectory>而不是递归方法调用来更有效地(但不那么简洁)地执行此操作的项目。

答案 2 :(得分:4)

更短的版本:

IEnumerable<IDirectory> Enumerate(IDirectory parent)
{
    return new[] { parent }.Concat(parent.Directories.SelectMany(Enumerate));
}

警告:此解决方案(以及发布的另一个解决方案)防范无限循环。理论上可以在对象结构中使用循环引用,在这种情况下,这将永远继续下去并永不停止。