Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/.net/25.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 具有LINQ消除递归的高效图遍历_C#_.net_Linq_Recursion_Graph - Fatal编程技术网

C# 具有LINQ消除递归的高效图遍历

C# 具有LINQ消除递归的高效图遍历,c#,.net,linq,recursion,graph,C#,.net,Linq,Recursion,Graph,今天,我要实现一种方法来遍历任意深度的图,并将其展平为单个可枚举图。相反,我先做了一点搜索,发现: public static IEnumerable<T> Traverse<T>(this IEnumerable<T> enumerable, Func<T, IEnumerable<T>> recursivePropertySelector) { foreach (T item in enumerable) {

今天,我要实现一种方法来遍历任意深度的图,并将其展平为单个可枚举图。相反,我先做了一点搜索,发现:

public static IEnumerable<T> Traverse<T>(this IEnumerable<T> enumerable, Func<T, IEnumerable<T>> recursivePropertySelector)
{
    foreach (T item in enumerable)
    {
        yield return item;

        IEnumerable<T> seqRecurse = recursivePropertySelector(item);

        if (seqRecurse == null) continue;
        foreach (T itemRecurse in Traverse(seqRecurse, recursivePropertySelector))
        {
            yield return itemRecurse;
        }
    }
}
公共静态IEnumerable遍历(此IEnumerable可枚举,Func recursivePropertySelector)
{
foreach(可枚举中的T项)
{
收益回报项目;
IEnumerable seqRecurse=recursivePropertySelector(项目);
如果(seqRecurse==null)继续;
foreach(遍历中的T项递归(seqRecurse,recursivePropertySelector))
{
收益-收益项目递归;
}
}
}
理论上这看起来不错,但在实践中,我发现它的性能要比使用等价的手写代码(根据情况而定)来浏览图形并执行任何需要执行的操作差得多。我怀疑这是因为在这个方法中,对于它返回的每一项,堆栈都必须展开到某个任意深度

我还怀疑,如果消除了递归,该方法的运行效率会更高。我也不太擅长消除递归

有人知道如何重写这个方法来消除递归吗

谢谢你的帮助

编辑: 非常感谢所有详细的回复。我尝试过基准测试原始解决方案和Eric的解决方案,以及不使用枚举器方法,而是使用lambda递归遍历,奇怪的是,lambda递归比其他两种方法都快得多

class Node
{
    public List<Node> ChildNodes { get; set; } 

    public Node()
    {
        ChildNodes = new List<Node>();
    }
}

class Foo
{
    public static void Main(String[] args) 
    {
        var nodes = new List<Node>();
        for(int i = 0; i < 100; i++)
        {
            var nodeA = new Node();
            nodes.Add(nodeA);
            for (int j = 0; j < 100; j++)
            {
                var nodeB = new Node();
                nodeA.ChildNodes.Add(nodeB);
                for (int k = 0; k < 100; k++)
                {
                    var nodeC = new Node();
                    nodeB.ChildNodes.Add(nodeC);
                    for(int l = 0; l < 12; l++)
                    {
                        var nodeD = new Node();
                        nodeC.ChildNodes.Add(nodeD);
                    }
                }
            }
        }            

        nodes.TraverseOld(node => node.ChildNodes).ToList();
        nodes.TraverseNew(node => node.ChildNodes).ToList();

        var watch = Stopwatch.StartNew();
        nodes.TraverseOld(node => node.ChildNodes).ToList();
        watch.Stop();
        var recursiveTraversalTime = watch.ElapsedMilliseconds;
        watch.Restart();
        nodes.TraverseNew(node => node.ChildNodes).ToList();
        watch.Stop();
        var noRecursionTraversalTime = watch.ElapsedMilliseconds;

        Action<Node> visitNode = null;
        visitNode = node =>
        {
            foreach (var child in node.ChildNodes)
                visitNode(child);
        };

        watch.Restart();
        foreach(var node in nodes)
            visitNode(node);
        watch.Stop();
        var lambdaRecursionTime = watch.ElapsedMilliseconds;
    }
}
类节点
{
公共列表子节点{get;set;}
公共节点()
{
ChildNodes=新列表();
}
}
福班
{
公共静态void Main(字符串[]args)
{
var节点=新列表();
对于(int i=0;i<100;i++)
{
var nodeA=新节点();
nodes.Add(nodeA);
对于(int j=0;j<100;j++)
{
var nodeB=新节点();
nodeA.ChildNodes.Add(nodeB);
对于(int k=0;k<100;k++)
{
var nodeC=新节点();
nodeB.ChildNodes.Add(nodeC);
对于(int l=0;l<12;l++)
{
var nodeD=新节点();
nodeC.ChildNodes.Add(nodeD);
}
}
}
}            
nodes.TraverseOld(node=>node.ChildNodes.ToList();
nodes.TraverseNew(node=>node.ChildNodes.ToList();
var watch=Stopwatch.StartNew();
nodes.TraverseOld(node=>node.ChildNodes.ToList();
看,停;
var recursiveTraversalTime=watch.ElapsedMilliseconds;
watch.Restart();
nodes.TraverseNew(node=>node.ChildNodes.ToList();
看,停;
var noRecursionTraversalTime=watch.ElapsedMilliseconds;
Action visitNode=null;
visitNode=节点=>
{
foreach(节点中的变量child.ChildNodes)
visitNode(儿童);
};
watch.Restart();
foreach(节点中的var节点)
visitNode(节点);
看,停;
var lambdaRecursionTime=watch.ElapsedMilliseconds;
}
}
其中TraverseOld是原始方法,TraverseNew是Eric的方法,显然lambda是lambda

在我的机器上,TraverseOld需要10127毫秒,TraverseNew需要3038毫秒,lambda递归需要1181毫秒


这是不是典型的枚举器方法(具有收益率返回)可能需要3倍于立即执行的时间?或者这里发生了什么事?

您可以在代码中使用队列。队列可以初始化为一个列表,其中一个元素等于顶部节点。然后必须从第一个元素开始遍历列表中的每个元素。如果第一个元素包含子节点,则将它们全部附加到队列的末尾。然后移动到下一个元素。当您到达队列的末尾时,图形将完全变平。

您可以通过复制递归如何与堆栈一起工作的基本知识来消除递归

  • 将第一项放在堆栈顶部
  • 当堆栈不为空时,从堆栈中弹出一个项目
  • 如果当前节点有子节点,请将它们添加到堆栈中
  • 返回当前项目
  • 转到步骤1 疯狂聪明的理论答案:


    你是对的,在代码中递归地遍历树和图,从而产生回报,这是效率低下的一个主要原因

    通常,您可以使用堆栈重写递归代码,其方式与在编译代码中通常实现的方式类似

    我没有机会尝试,但这应该可以:

    public static IEnumerable<T> Traverse<T>(this IEnumerable<T> enumerable, Func<T, IEnumerable<T>> recursivePropertySelector) {
        var stack = new Stack<IEnumerable<T>>();
        stack.Push(enumerable);
        while (stack.Count != 0) {
            enumerable = stack.Pop();
            foreach (T item in enumerable) {
                yield return item;
                var seqRecurse = recursivePropertySelector(item);
                if (seqRecurse != null) {
                    stack.Push(seqRecurse);
                }
            }
        }
    }
    
    公共静态IEnumerable遍历(此IEnumerable可枚举,Func recursivePropertySelector){
    var stack=新堆栈();
    stack.Push(可枚举);
    while(stack.Count!=0){
    enumerable=stack.Pop();
    foreach(可枚举中的T项){
    收益回报项目;
    var seqRecurse=recursivePropertySelector(项目);
    if(seqRecurse!=null){
    stack.Push(seqRecurse);
    }
    }
    }
    }
    
    首先,你是绝对正确的;如果图有n个平均深度为d的节点,那么朴素的嵌套迭代器将生成一个时间为O(n*d)、堆栈为O(d)的解。如果d是n的很大一部分,那么这可以成为一个O(n2)算法,如果d是大的,那么你可以完全破坏堆栈

    如果您对嵌套迭代器的性能分析感兴趣,请参阅前C#编译器开发人员Wes Dyer的博客文章:

    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);
        }
    }
    
    然后遍历是A,B,D,C,D,C,D。如果你有一个循环图或互连图,那么你想要的是
    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 ;
    }
    
              A
             / \
            B-->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);
        }
    }