通过递归检查父子关系C#构建树类型列表

Posted

技术标签:

【中文标题】通过递归检查父子关系C#构建树类型列表【英文标题】:Build tree type list by recursively checking parent-child relationship C# 【发布时间】:2013-04-07 20:32:58 【问题描述】:

我有一个类,它有一个自身的列表,因此可以用树结构表示。

我正在提取这些类的平面列表,并希望将其展开。

public class Group

     public int ID get;set;

     public int? ParentID get;set;

     public List<Group> Children get;set;


我希望能够做到以下几点

List<Group> flatList = GetFlatList() //I CAN ALREADY DO THIS
List<Group> tree = BuildTree(flatList);

ParentID 与其父组上的 ID 属性相关,如果这不明显的话。

编辑

对于我返回列表而不是单个对象的原因有些困惑。

我正在构建一个包含项目列表的 UI 元素,每个项目都有一个子项。所以初始列表没有根节点。到目前为止,似乎所有解决方案都不起作用。

这意味着我本质上需要一个使用 Group 类的树型结构列表。

【问题讨论】:

该平面列表是否以任何方式排序?就像,父母总是出现在它的任何孩子之前,你能保证这样的顺序吗?或者你可能会在父母之前得到孩子? 使用 Dictionary&lt;int, Group&gt; 将 id 映射到 Groups,然后使用它来查找要将项目添加到哪个父级。除了树是递归数据结构这一事实之外,这也绝不涉及递归。 flatList的类型是什么?那里的var 没有给我们太多信息。 我在上面做了一些修改。顺序无关紧要。 查看这篇文章:scip.be/index.php?Page=ArticlesNET23&Lang=NL 【参考方案1】:

我不知道你为什么希望你的 BuildTree 方法返回 List&lt;Group&gt; - 树需要有根节点,所以你应该期望它返回单个 Group 元素,而不是列表。

我会在IEnumerable&lt;Group&gt; 上创建一个扩展方法:

public static class GroupEnumerable

    public static IList<Group> BuildTree(this IEnumerable<Group> source)
    
        var groups = source.GroupBy(i => i.ParentID);

        var roots = groups.FirstOrDefault(g => g.Key.HasValue == false).ToList();

        if (roots.Count > 0)
        
            var dict = groups.Where(g => g.Key.HasValue).ToDictionary(g => g.Key.Value, g => g.ToList());
            for (int i = 0; i < roots.Count; i++)
                AddChildren(roots[i], dict);
        

        return roots;
    

    private static void AddChildren(Group node, IDictionary<int, List<Group>> source)
    
        if (source.ContainsKey(node.ID))
        
            node.Children = source[node.ID];
            for (int i = 0; i < node.Children.Count; i++)
                AddChildren(node.Children[i], source);
        
        else
        
            node.Children = new List<Group>();
        
    

用法

var flatList = new List<Group>() 
    new Group()  ID = 1, ParentID = null ,    // root node
    new Group()  ID = 2, ParentID = 1 ,
    new Group()  ID = 3, ParentID = 1 ,
    new Group()  ID = 4, ParentID = 3 ,
    new Group()  ID = 5, ParentID = 4 ,
    new Group()  ID = 6, ParentID = 4 
;


var tree = flatList.BuildTree();

【讨论】:

我在上面回答了我为什么要返回一个列表。初始列表没有根节点。它实际上应该是一个树型结构的列表。 完美,正是我需要的!感谢并为您的困惑感到抱歉。 有时groups.FirstOrDefault(g =&gt; g.Key.HasValue == false) 返回null 请在此处指定“groups”、“roots”、“dict”的类型,而不是“var”。我不明白变量的类型。 有没有没有Linq和Lambda表达式的解决方案?【参考方案2】:

以下是您可以在一行中完成此操作的方法:

static void BuildTree(List<Group> items)

    items.ForEach(i => i.Children = items.Where(ch => ch.ParentID == i.ID).ToList());

你可以这样称呼它:

BuildTree(flatList);

如果最后你想获取其父节点为空的节点(即***节点),你可以简单地这样做:

static List<Group> BuildTree(List<Group> items)

    items.ForEach(i => i.Children = items.Where(ch => ch.ParentID == i.ID).ToList());
    return items.Where(i => i.ParentID == null).ToList();

如果你想让它成为一个扩展方法,你可以在方法签名中添加this

static List<Group> BuildTree(this List<Group> items)

那么你可以这样称呼它:

var roots = flatList.BuildTree();

【讨论】:

您提供的解决方案为每个节点生成树。如何为那些父键为空的节点生成树? 在任何一种情况下都需要为每个节点生成一棵树,但是如果最后你想获取其父键为空的节点,我已经修改了我的答案以显示如何执行此操作。 @JLRishe,我怎么知道每个TreeNode的深度? @ebramkhalil 您可以像这样在Group 类上定义一个属性:public int Depth get return 1 + (Children != null &amp;&amp; Children.Any() ? Children.Max(c =&gt; c.Depth) : 0); 【参考方案3】:

我尝试了建议的解决方案,并发现它们为我们提供了 O(n^2) 复杂度。

就我而言(我有大约 50k 项要构建到树中),这是完全不可接受的。

我得出了以下解决方案(假设每个项目只有一个父项并且所有父项都存在于列表中),复杂度为 O(n*log(n)) [n 次 getById,getById 有 O(log(n) ) 复杂性]:

static List<Item> BuildTreeAndReturnRootNodes(List<Item> flatItems)

    var byIdLookup = flatItems.ToLookup(i => i.Id);
    foreach (var item in flatItems)
    
        if (item.ParentId != null)
        
            var parent = byIdLookup[item.ParentId.Value].First();
            parent.Children.Add(item);
        
    
    return flatItems.Where(i => i.ParentId == null).ToList();

完整代码sn-p:

class Program

    static void Main(string[] args)
    
        var flatItems = new List<Item>()
        
            new Item(1),
            new Item(2),
            new Item(3, 1),
            new Item(4, 2),
            new Item(5, 4),
            new Item(6, 3),
            new Item(7, 5),
            new Item(8, 2),
            new Item(9, 3),
            new Item(10, 9),
        ;
        var treeNodes = BuildTreeAndReturnRootNodes(flatItems);
        foreach (var n in treeNodes)
        
            Console.WriteLine(n.Id + " number of children: " + n.Children.Count);
        
    
    // Here is the method
    static List<Item> BuildTreeAndReturnRootNodes(List<Item> flatItems)
    
        var byIdLookup = flatItems.ToLookup(i => i.Id);
        foreach (var item in flatItems)
        
            if (item.ParentId != null)
            
                var parent = byIdLookup[item.ParentId.Value].First();
                parent.Children.Add(item);
            
        
        return flatItems.Where(i => i.ParentId == null).ToList();
    
    class Item
    
        public readonly int Id;
        public readonly int? ParentId;

        public Item(int id, int? parent = null)
        
            Id = id;
            ParentId = parent;
        
        public readonly List<Item> Children = new List<Item>();
    

【讨论】:

很抱歉,这只是想法插图。我将添加完整的 sn-p。 谢谢,我的朋友。我来自巴西,我在我的项目中使用了你的解决方案。

以上是关于通过递归检查父子关系C#构建树类型列表的主要内容,如果未能解决你的问题,请参考以下文章

list构建数据库父子关系

java List递归排序,无序的列表按照父级关系进行排序(两种排序类型)

如何知道父子 C# 列表中的深度计数 [关闭]

递归构建分层JSON树?

oracle 父子关系

如何避免父子jpa关系中的json响应递归?