C# 如何加速这个递归函数?

C# 如何加速这个递归函数?,c#,recursion,ienumerable,C#,Recursion,Ienumerable,我有一个递归函数,它从大约2000条记录的IEnumerable中构建一个节点列表。该过程目前大约需要9秒才能完成,并已成为一个主要的性能问题。该功能用于: a) 按层次对节点进行排序 b) 计算每个节点的深度 这是一个简单的例子: public class Node { public string Id { get; set; } public string ParentId { get; set; } public int Depth { get; set; } }

我有一个递归函数,它从大约2000条记录的IEnumerable中构建一个节点列表。该过程目前大约需要9秒才能完成,并已成为一个主要的性能问题。该功能用于:

a) 按层次对节点进行排序

b) 计算每个节点的深度

这是一个简单的例子:

public class Node
{
    public string Id { get; set; }
    public string ParentId { get; set; }
    public int Depth { get; set; }
}

private void GetSortedList()
{
// next line pulls the nodes from the DB, not included here to simplify the example         
IEnumerable<Node> ie = GetNodes();

    var l = new List<Node>();
    foreach (Node n in ie)
    {
        if (string.IsNullOrWhiteSpace(n.ParentId))
        {
            n.Depth = 1;
            l.Add(n);
            AddChildNodes(n, l, ie);
        }
    }
}

private void AddChildNodes(Node parent, List<Node> newNodeList, IEnumerable<Node> ie)
{
    foreach (Node n in ie)
    {
        if (!string.IsNullOrWhiteSpace(n.ParentId) && n.ParentId == parent.Id)
        {
            n.Depth = parent.Depth + 1;
            newNodeList.Add(n);
            AddChildNodes(n, newNodeList, ie);
        }
    }
}
公共类节点
{
公共字符串Id{get;set;}
公共字符串ParentId{get;set;}
公共整数深度{get;set;}
}
私有void GetSortedList()
{
//下一行从数据库中提取节点,这里不包括这些节点,以简化示例
IEnumerable ie=GetNodes();
var l=新列表();
foreach(ie中的节点n)
{
if(string.IsNullOrWhiteSpace(n.ParentId))
{
n、 深度=1;
l、 添加(n);
AddChildNodes(n,l,ie);
}
}
}
私有void AddChildNodes(节点父节点、列表newNodeList、IEnumerable ie)
{
foreach(ie中的节点n)
{
如果(!string.IsNullOrWhiteSpace(n.ParentId)&&n.ParentId==parent.Id)
{
n、 深度=父级。深度+1;
添加(n);
AddChildNodes(n,newNodeList,ie);
}
}
}

重写此文件以最大限度地提高性能的最佳方法是什么?我尝试过使用yield关键字,但我不确定这是否会得到我想要的结果。我也读过关于使用堆栈的内容,但我发现没有一个示例使用父ID(它们使用子节点列表),因此我对如何使用它有点困惑。

递归不是导致性能问题的原因。真正的问题是,在每次递归调用
AddChildNodes
时,您都会遍历整个列表以查找当前父节点的子节点,因此您的算法最终是O(n^2)

为了解决这个问题,您可以创建一个字典,为每个节点Id提供其所有子节点的列表。这可以在列表的一次传递中完成。然后,您可以从根Id(“”)开始,递归地访问它的每个子级(即“深度优先遍历”)。这将只访问每个节点一次。所以整个算法是O(n)。代码如下所示

调用
GetSortedList
后,排序结果显示在
result
中。请注意,如果愿意,您可以在
GetSortedList
中生成
子变量
结果
局部变量,并将它们作为参数传递给
DepthFirstTraversal
。但这不必要地减慢了递归调用的速度,因为这两个参数在每个递归调用上总是具有相同的值

您可以使用堆栈摆脱递归,但是性能的提高可能不值得

Dictionary<string, List<Node>> children = null; 
List<Node> result = null;

private void GetSortedList()
{
    var ie = data;
    children = new Dictionary<string,List<Node>>();

    // construct the dictionary 
    foreach (var n in ie) 
    {
        if (!children.ContainsKey(n.ParentId)) 
        {
            children[n.ParentId] =  new List<Node>();
        }
        children[n.ParentId].Add(n);
    }

    // Depth first traversal
    result = new List<Node>();
    DepthFirstTraversal("", 1);

    if (result.Count() !=  ie.Count()) 
    {
        // If there are cycles, some nodes cannot be reached from the root,
        // and therefore will not be contained in the result. 
        throw new Exception("Original list of nodes contains cycles");
    }
}

private void DepthFirstTraversal(string parentId, int depth)
{
    if (children.ContainsKey(parentId))
    {
        foreach (var child in children[parentId])
        {
            child.Depth = depth;
            result.Add(child);
            DepthFirstTraversal(child.Id, depth + 1);
        }
    }
}
字典子项=null;
列表结果=空;
私有void GetSortedList()
{
var ie=数据;
儿童=新字典();
//构建词典
foreach(ie中的变量n)
{
如果(!children.ContainsKey(n.ParentId))
{
children[n.ParentId]=新列表();
}
子项[n.ParentId]。添加(n);
}
//深度优先遍历
结果=新列表();
深度优先遍历(“,1);
if(result.Count()!=ie.Count())
{
//如果存在循环,则无法从根节点访问某些节点,
//因此不会包含在结果中。
抛出新异常(“原始节点列表包含循环”);
}
}
私有void DepthFirstTraversal(字符串parentId,int depth)
{
if(children.ContainsKey(parentId))
{
foreach(子对象[parentId]中的变量child)
{
深度=深度;
结果:添加(儿童);
深度第一次遍历(child.Id,深度+1);
}
}
}

递归不是导致性能问题的原因。真正的问题是,在每次递归调用
AddChildNodes
时,您都会遍历整个列表以查找当前父节点的子节点,因此您的算法最终是O(n^2)

为了解决这个问题,您可以创建一个字典,为每个节点Id提供其所有子节点的列表。这可以在列表的一次传递中完成。然后,您可以从根Id(“”)开始,递归地访问它的每个子级(即“深度优先遍历”)。这将只访问每个节点一次。所以整个算法是O(n)。代码如下所示

调用
GetSortedList
后,排序结果显示在
result
中。请注意,如果愿意,您可以在
GetSortedList
中生成
子变量
结果
局部变量,并将它们作为参数传递给
DepthFirstTraversal
。但这不必要地减慢了递归调用的速度,因为这两个参数在每个递归调用上总是具有相同的值

您可以使用堆栈摆脱递归,但是性能的提高可能不值得

Dictionary<string, List<Node>> children = null; 
List<Node> result = null;

private void GetSortedList()
{
    var ie = data;
    children = new Dictionary<string,List<Node>>();

    // construct the dictionary 
    foreach (var n in ie) 
    {
        if (!children.ContainsKey(n.ParentId)) 
        {
            children[n.ParentId] =  new List<Node>();
        }
        children[n.ParentId].Add(n);
    }

    // Depth first traversal
    result = new List<Node>();
    DepthFirstTraversal("", 1);

    if (result.Count() !=  ie.Count()) 
    {
        // If there are cycles, some nodes cannot be reached from the root,
        // and therefore will not be contained in the result. 
        throw new Exception("Original list of nodes contains cycles");
    }
}

private void DepthFirstTraversal(string parentId, int depth)
{
    if (children.ContainsKey(parentId))
    {
        foreach (var child in children[parentId])
        {
            child.Depth = depth;
            result.Add(child);
            DepthFirstTraversal(child.Id, depth + 1);
        }
    }
}
字典子项=null;
列表结果=空;
私有void GetSortedList()
{
var ie=数据;
儿童=新字典();
//构建词典
foreach(ie中的变量n)
{
如果(!children.ContainsKey(n.ParentId))
{
children[n.ParentId]=新列表();
}
子项[n.ParentId]。添加(n);
}
//深度优先遍历
结果=新列表();
深度优先遍历(“,1);
if(result.Count()!=ie.Count())
{
//如果存在循环,则无法从根节点访问某些节点,
//因此不会包含在结果中。
抛出新异常(“原始节点列表包含循环”);
}
}
私有void DepthFirstTraversal(字符串parentId,int depth)
{
if(children.ContainsKey(parentId))
{
foreach(子对象[parentId]中的变量child)
{
深度=深度;
结果:添加(儿童);
深度第一次遍历(child.Id,深度+1);
}
}
}