有效搜索树结构中的所有节点

时间:2018-09-17 21:23:48

标签: c# recursion treenode

我正在使用一个由单个“ 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,我从不做任何事情...这让我感到效率低下。我想知道是否有更直接的方法来导航这种结构...我确定我只是想念一个窍门。

2 个答案:

答案 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#