我正在使用一个由单个“ root” TreeNodeDefinition记录组成的树类型结构,该记录除其他外可以包含其他TreeNodeDefinition类的列表,然后每个此类可以包含另一个List等,依此类推。
我基本上希望能够遍历树结构中的所有节点,并检查每个节点是否满足条件,然后将该定义记录添加到列表中。我想出了一种方法来做到这一点,但我忍不住想到了一种更有效的方法:
List<ITreeNodeDefinition> treeNodeDefinitions = new List<ITreeNodeDefinition>();
treeNodeDefinitions = treeFactory.SearchNodesByRelationshipId(treeNodeDefinition, relationshipId, new List<ITreeNodeDefinition>());
其中第一个参数是我的根节点定义记录,第二个参数是我需要比较每个节点的参数,最后我要传递一个空列表,每次节点与我的支票匹配时我都要填充该列表。方法如下:
public List<ITreeNodeDefinition> SearchNodesByRelationshipId(ITreeNodeDefinition treeNodeDefinition, int? relationshipId, List<ITreeNodeDefinition> tndList)
{
if (treeNodeDefinition.RelationshipId == relationshipId)
{
tndList.Add(treeNodeDefinition);
}
if (treeNodeDefinition.Nodes.Count != 0)
{
foreach (ITreeNodeDefinition nodeDefinition in treeNodeDefinition.Nodes)
{
List<ITreeNodeDefinition> tempTable = this.SearchNodesByRelationshipId(nodeDefinition, relationshipId, tndList);
}
}
return tndList;
}
如您所见,该方法正在为treeNodeDefinition.Nodes列表中找到的每个子节点调用自身。这返回到一个tempTable,我从不做任何事情...这让我感到效率低下。我想知道是否有更直接的方法来导航这种结构...我确定我只是想念一个窍门。
答案 0 :(得分:0)
您可以使用显式堆栈来解决此问题,并完全避免递归:
public static IEnumerable<ITreeNodeDefinition> DepthFirstSearch(ITreeNodeDefinition root, int? relationshipId)
{
var stack = new Stack<ITreeNodeDefinition>();
stack.Push(root);
while(stack.Count > 0)
{
var current = stack.Pop();
if (current.RelationshipId == relationshipId)
yield return current;
foreach(var node in current.Nodes)
stack.Push(node);
}
}
但是要添加到它上,根本不对关系id检查进行硬编码可能会更简单,更灵活,如果您发现自己通常试图遍历树结构,则仅对结果进行过滤:
var matches = treeFactory.Traverse(root)
.Where(t => t.RelationshipId == 5)
.ToList();
对此进行扩展,通过使用搜索谓词,您可以像LINQ一样将这种搜索功能构建到方法中,您可以像这样实现:
public static IEnumerable<ITreeNodeDefinition> DepthFirstSearch(ITreeNodeDefinition root, Func<ITreeNodeDefinition, bool> predicate)
{
var stack = new Stack<ITreeNodeDefinition>();
stack.Push(root);
while (stack.Count > 0)
{
var current = stack.Pop();
if (predicate(current))
yield return current;
foreach (var node in current.Nodes)
stack.Push(node);
}
}
好处是遍历时不会在一种特定的搜索情况下进行硬编码。通过RelationshipId
搜索来调用它是:
var matches = treeFactory.DepthFirstSearch(root, t => t.RelationshipId == 5)
.ToList();
为完整起见,这是广度优先搜索的示例。请注意,Queue<T>
与Stack<T>
在遍历顺序如何变化方面有所不同:
public static IEnumerable<ITreeNodeDefinition> BreadthFirstSearch(ITreeNodeDefinition root, Func<ITreeNodeDefinition, bool> predicate)
{
var queue = new Queue<ITreeNodeDefinition>();
queue.Enqueue(root);
while (queue.Count > 0)
{
var current = queue.Dequeue();
if (predicate(current))
yield return current;
foreach (var node in current.Nodes)
queue.Enqueue(node);
}
}
对于您的用例,除了以广度优先的顺序进行排序(通常在列出节点时更好)的方式之外,任何一个都可能没有优势。
答案 1 :(得分:0)
关于问题/代码,我认为问题与一件事无关(就像您说的tempTable冗余变量一样),但它与多个领域有关,我将尝试重点介绍这些领域/问题。
1)理论。在迭代树之前,您需要了解的第一件事-有两种方法来迭代树' breadth-first '和'< em> depth-first ”。它们可以通过“ 递归”和“ 循环”实现。关于它的文章很多。
我建议您阅读一些有关这些方法的文章,其中一些:
2)您从性能pov中注意到不重要的问题。是的,当您说将上一个调用的结果存储到“ tempTable”不是很好时,您是对的。返回“ tempTable”对性能或内存没有太大影响,因为“ tempTamble ”被引用为与“ tndList ”相同的对象。返回方法参数不会带来“ 低效率”。它影响的唯一一件事是-不干净的代码和堆栈中的几个字节。确实,您不需要在方法中返回任何内容。为什么返回清单?
我建议您阅读关于价值和参考类型。一些材料
我稍微更改了您的代码,现在返回 void :
public void SearchNodesByRelationshipId(ITreeNodeDefinition treeNodeDefinition, int? relationshipId, List<ITreeNodeDefinition> tndList)
{
if (treeNodeDefinition.RelationshipId == relationshipId)
{
tndList.Add(treeNodeDefinition);
}
if (treeNodeDefinition.Nodes.Count != 0)
{
foreach (ITreeNodeDefinition nodeDefinition in treeNodeDefinition.Nodes)
{
this.SearchNodesByRelationshipId(nodeDefinition, relationshipId, tndList);
}
}
}
3)另一个问题。重要。您进行迭代的方式是“ 深度优先”递归。这种方法不够可靠,可能会导致'StackOverflowException'。由于长链的递归方法调用。
我建议您在树的上下文中阅读 iteration 和递归算法,并实现 iteration 方法。
仅供参考:还有一种方法可以避免' recursion '方法的'stackOverfrwException'- tail recursion ,但是afaik,{{ 1}},但是C#
和其他功能语言中都存在这种机制。
简而言之,迭代方法的工作方式是伪代码:
F#