通过递归检查父子关系构建树型列表
本文关键字:构建 树型 列表 父子关系 检查 递归 | 更新日期: 2023-09-27 18:07:50
我有一个类,它有一个自己的列表,所以它可以在树结构中表示。
我正在拉这些类的一个扁平列表,并希望将其非扁平化。
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);
与父组的ID属性相关的ParentID
编辑
为什么我返回一个列表而不是一个对象,这有些令人困惑。
我正在构建一个UI元素,它有一个项目列表,每个为什么有一个孩子。所以初始列表没有根节点。到目前为止,似乎所有的解决方案都不起作用。
这意味着我本质上需要一个使用Group类的树型结构列表
我不知道为什么你想要你的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();
你可以这样做:
static void BuildTree(List<Group> items)
{
items.ForEach(i => i.Children = items.Where(ch => ch.ParentID == i.ID).ToList());
}
你可以这样称呼它:
BuildTree(flatList);
如果最后你想获得父节点为null的节点(即顶级节点),你可以简单地这样做:
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();
我尝试了建议的解决方案,发现它们给我们带来了大约O(n^2)的复杂度。
在我的情况下(我有大约50,000个项目要构建到树中),这是完全不可接受的。
我得到了以下解决方案(假设每个项只有一个父项,并且所有父项都存在于列表中)复杂度为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>();
}
}
public 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 > ();
}
public class BuildTree {
public 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();
}
}
public class TreeToFlatternBack {
public static IEnumerable < Item > GetNodes(Item node) {
if (node == null) {
yield
break;
}
yield
return node;
foreach(var n in node.Children) {
foreach(var innerN in GetNodes(n)) {
yield
return innerN;
}
}
}
}
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),
};
Console.WriteLine();
Console.WriteLine("--------------------Build a Tree--------------------");
Console.WriteLine();
var treeNodes = BuildTree.BuildTreeAndReturnRootNodes(flatItems);
foreach(var n in treeNodes) {
Console.WriteLine(n.Id + " number of children: " + n.Children.Count);
}
Console.WriteLine();
Console.WriteLine("--------------------Tree Back to Flattern--------------------");
Console.WriteLine();
List < Item > BackToflatItems = new List < Item > ();
foreach(var item in treeNodes) {
BackToflatItems.AddRange(TreeToFlatternBack.GetNodes(item));
}
foreach(var n in BackToflatItems.OrderBy(x => x.Id)) {
Console.WriteLine(n.Id + " number of children: " + n.Children.Count);
}
}
}