我有一个递归函数,它从大约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);
}
}
}
重写此内容以最大限度提高效果的最佳方法是什么?我已经尝试过yield关键字,但我不确定这会得到我想要的结果。我也读过关于使用堆栈但没有找到使用父ID的例子(他们使用子节点列表),所以我对如何处理它感到有点困惑。
答案 0 :(得分:2)
递归不是导致性能问题的原因。真正的问题是,在每次递归调用AddChildNodes
时,您遍历整个列表以查找当前父项的子项,因此您的算法最终为O(n ^ 2)。
要解决此问题,您可以创建一个字典,为每个节点ID提供其所有子节点的列表。这可以在列表的单次传递中完成。然后,您可以从根Id(“”)开始并递归访问其每个子节点(即“深度优先遍历”)。这将只访问每个节点一次。所以整个算法都是O(n)。代码如下所示。
调用GetSortedList
后,排序后的结果位于result
。请注意,您可以在children
中创建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);
}
}
}