简体   繁体   English

如何加快此递归功能?

[英]How can I speed up this recursive function?

I have a recursion function that builds a node list from an IEnumerable of about 2000 records. 我有一个递归函数,该函数从IEnumerable约2000条记录中构建节点列表。 The procedure currently takes around 9 seconds to complete and has become a major performance issue. 该过程目前大约需要9秒钟才能完成,已成为主要的性能问题。 The function serves to: 该功能用于:

a) sort the nodes hierarchically a)对节点进行分层排序

b) calculate the depth of each node b)计算每个节点的深度

This is a stripped down example: 这是一个简化的示例:

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);
        }
    }
}

What would be the best way to rewrite this to maximize performance? 重写此代码以最大化性能的最佳方法是什么? I've experimented with the yield keyword but I'm not sure that will get me the result I am looking for. 我已经尝试过yield关键字,但是不确定是否可以得到想要的结果。 I've also read about using a stack but none of the examples I have found use parent IDs (they use child node lists instead), so I am a little confused on how to approach it. 我也读过有关使用堆栈的信息,但是我发现没有一个示例使用父ID(而是使用子节点列表),因此我对如何使用它感到有些困惑。

Recursion is not what is causing your performance problem. 递归不是导致您的性能问题的原因。 The real problem is that on each recursive call to AddChildNodes , you traverse the entire list to find the children of the current parent, so your algorithm ends up being O(n^2). 真正的问题是,在对AddChildNodes每个递归调用上,您遍历整个列表以查找当前父级的子级,因此您的算法最终为O(n ^ 2)。

To get around this, you can create a dictionary that, for each node Id, gives a list of all its children. 为了解决这个问题,您可以创建一个字典,该字典针对每个节点ID给出其所有子项的列表。 This can be done in a single pass of the list. 可以通过列表的一次操作来完成。 Then, you can start with the root Id ("") and recursively visit each of its children (ie a "depth first traversal"). 然后,您可以从根ID(“”)开始并递归访问其每个子级(即“深度优先遍历”)。 This will visit each node exactly once. 这将访问每个节点一次。 So the entire algorithm is O(n). 因此整个算法为O(n)。 Code is shown below. 代码如下所示。

After calling GetSortedList , the sorted result is in result . 打完电话后GetSortedList ,排序结果是result Note that you could make children and result local variables in GetSortedList and pass them as parameters to DepthFirstTraversal , if you prefer. 请注意,您可以让childrenresult的局部变量GetSortedList ,并将它们作为参数传递给DepthFirstTraversal ,如果你喜欢。 But that unnecessarily slows down the recursive calls, since those two parameters would always have the same values on each recursive call. 但这不必要地减慢了递归调用的速度,因为这两个参数在每个递归调用上始终具有相同的值。

You can get rid of the recursion using stacks, but the performance gain would probably not be worth it. 您可以使用堆栈摆脱递归,但是性能提升可能不值得。

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);
        }
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM