24

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

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

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 类的树型结构列表。

4

3 回答 3

44

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

我会创建一个扩展方法IEnumerable<Group>

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();
于 2013-04-07T20:53:53.257 回答
25

以下是如何在一行中执行此操作:

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();
于 2013-04-07T20:48:31.903 回答
10

我尝试了建议的解决方案,并发现它们为我们提供了 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();
}

完整的代码片段:

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>();
    }
}
于 2015-01-28T00:54:09.567 回答