C# 平整结构与树结构互转
下面是示例代码
数结构定义
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApplication1
{
internal class Program
{
public static void Main(string[] args)
{
// List<Node> nodes = new List<Node>();
// var root = new Node() { Id = "1", Data = "第一个节点" };
// nodes.Add(root);
// for (int i = 2; i < 30; i++)
// {
// var childNode = new Node() { Id = i.ToString(), PId = (i - 1).ToString(), Data = $"第{i}个节点" };
// nodes.Add(childNode);
// }
//
// foreach (var node in nodes)
// {
// Console.WriteLine(node);
// }
var newNodes = new List<Node>();
newNodes.Add(new Node() { Id = "1", PId = "", Data = "1节点" });
newNodes.Add(new Node() { Id = "4", PId = "2", Data = "4节点" });
newNodes.Add(new Node() { Id = "7", PId = "4", Data = "7节点" });
newNodes.Add(new Node() { Id = "5", PId = "2", Data = "5节点" });
newNodes.Add(new Node() { Id = "2", PId = "1", Data = "2节点" });
newNodes.Add(new Node() { Id = "3", PId = "1", Data = "3节点" });
// var treeList = BuildTree(newNodes);
// var treeList = BuildTreeV1(newNodes);
// var treeList = BuildTreeV3(newNodes);
var treeList = newNodes.Where(x => x.IsRoot).ToList();
foreach (var node in treeList)
{
BuildTreeV4(node, n => newNodes.Where(x => x.PId == n.Id));
}
var treeInfo = new Node()
{
Id = "1",
Data = "1节点",
Children = new List<Node>()
{
new Node()
{
Id = "2",
PId = "1",
Data = "2节点",
Children = new List<Node>()
{
new Node()
{
Id = "4",
PId = "2",
Data = "4节点",
Children = new List<Node>()
{
new Node()
{
Id = "7",
PId = "2",
Data = "7节点",
}
}
},
new Node()
{
Id = "5",
PId = "2",
Data = "5节点",
}
}
},
new Node()
{
Id = "3",
PId = "1",
Data = "3节点",
}
},
};
var myChildren = new List<Node>();
// GetMyChildren(treeInfo, myChildren);
GetMyChildrenV2(treeInfo, myChildren);
GetMyChildrenV3(treeInfo, myChildren);
foreach (var node in myChildren)
{
Console.WriteLine(node);
}
Console.ReadLine();
}
//预先知道有多少层级就写多少层for循环
//缺点 局限性太强 只能适应已知层级的场景
private static List<Node> BuildTree(List<Node> nodes)
{
var treeItems = new List<Node>();
//找出根节点
var roots = nodes.Where(x => x.IsRoot).ToList();
//遍历根节点 找出子节点
foreach (var currentNode in roots)
{
//筛选出子节点
var childNodes = nodes.Where(x => x.PId == currentNode.Id).ToList();
if (!childNodes.Any())
{
break; //结束
}
foreach (var childNode in childNodes)
{
//加入到自己的子节点集合中
if (currentNode.Children == null)
{
currentNode.Children = new List<Node>();
}
currentNode.Children.Add(childNode);
//若我的子节点有自己的子节点--找出子节点的子节点集合
var grandChildNodes = nodes.Where(x => x.PId == childNode.Id).ToList();
if (!grandChildNodes.Any())
{
break;
}
foreach (var grandChildNode in grandChildNodes)
{
//加入到自己的子节点集合中
if (childNode.Children == null)
{
childNode.Children = new List<Node>();
}
childNode.Children.Add(grandChildNode);
//若我的子节点有自己的子节点--找出子节点的子节点集合
//..........这仿佛是一种重复性的工作...........
}
}
//总之走完上面的操作当前节点的子节点均已归位
treeItems.Add(currentNode);
}
return treeItems;
}
//递归方式
//缺点 开销太大 层级过深会爆栈
private static List<Node> BuildTreeV1(List<Node> nodes)
{
var treeItems = new List<Node>();
//找出根节点
var roots = nodes.Where(x => x.IsRoot).ToList();
//遍历根节点 找出子节点
foreach (var currentNode in roots)
{
FindMyChildren(currentNode, nodes);
//总之走完上面的操作当前节点的子节点均已归位
treeItems.Add(currentNode);
}
return treeItems;
}
private static void FindMyChildren(Node root, List<Node> nodes)
{
var childNodes = nodes.Where(x => x.PId == root.Id).ToList();
// if (!childNodes.Any())
// {
// return;
// }
foreach (var childNode in childNodes)
{
if (root.Children == null)
{
root.Children = new List<Node>();
}
root.Children.Add(childNode);
FindMyChildren(childNode, nodes);
}
}
//装入字典使查找更高效
//在当前循环中 找当前节点的父亲节点 将自己放到父亲的子节点集合中
private static List<Node> BuildTreeV3(List<Node> nodes)
{
var treeItems = new Dictionary<string, Node>();
if (nodes == null || nodes.Count == 0)
{
return null;
}
foreach (var currentNode in nodes)
{
treeItems.Add(currentNode.Id, currentNode);
}
foreach (var currentItem in treeItems)
{
if (currentItem.Value == null || currentItem.Value.IsRoot)
{
continue;
}
var parent = treeItems.ContainsKey(currentItem.Value.PId) ? treeItems[currentItem.Value.PId] : null;
if (parent == null)
{
continue;
}
if (parent.Children == null)
{
parent.Children = new List<Node>();
}
parent.Children.Add(currentItem.Value);
}
return treeItems.Values.Where(x => x.IsRoot).ToList();
}
//是不是可以将父子关系 放到一个委托中 这样代码通用性更好
private static void BuildTreeV4(Node currentNode, Func<Node, IEnumerable<Node>> childSelector)
{
var childList = childSelector?.Invoke(currentNode);
if (childList == null)
{
return;
}
foreach (var child in childList)
{
if (currentNode.Children == null)
{
currentNode.Children = new List<Node>();
}
currentNode.Children.Add(child);
BuildTreeV4(child, childSelector);
}
}
//给定一个节点如何获取所有的子节点呢?
//思路一 查找所有子节点 遍历子节点 找到子节点的子节点集合
private static void GetMyChildren(Node root, List<Node> childNodes)
{
if (root == null || root.Children == null)
{
return;
}
foreach (var child in root.Children)
{
childNodes.Add(child);
GetMyChildren(child, childNodes);
}
}
//思路二 非递归方式 上面操作[先访问的节点先加入到集合 后访问的节点后加入到集合]有点先进先出的感觉 用队列
/*
* 1
* 2 3
* 4
* 5 6
* 访问路径为 1 2 3 4 5 6
*/
//这种往下 每一层先访问所有子节点的方式 在树的遍历中叫 广度优先遍历
private static void GetMyChildrenV2(Node root, List<Node> childNodes)
{
if (root == null|| root.Children == null|| root.Children.Count == 0)
{
return;
}
var queue = new Queue<Node>(root.Children);
childNodes.AddRange(root.Children);
while (queue.Any())
{
var currentNode = queue.Dequeue();
if (currentNode != null)
{
if (currentNode.Children != null)
{
childNodes.AddRange(currentNode.Children);
foreach (var child in currentNode.Children)
{
queue.Enqueue(child);
}
}
}
}
}
//思路三 非递归方式 先紧着一个节点的第一个子节点往下查找
/*
* 1
* 2 3
* 4
* 5 6
* 访问路径为 1 2 4 5 6 3
*/
//就是访问到有子节点后 继续向下找子节点 知道没有子节点后 返回上一级 再访问父节点下的另一个兄弟节点
//这种先访问左子树 再访问右子树的方式 在树的遍历中 称为深度优先--前序遍历
private static void GetMyChildrenV3(Node root, List<Node> childNodes)
{
if (root == null || root.Children == null || root.Children.Count == 0)
{
return;
}
var stack = new Stack<Node>();
for (int i = root.Children.Count - 1; i >= 0; i--)
{
stack.Push(root.Children[i]);
}
while (stack.Any())
{
var currentNode = stack.Pop();
childNodes.Add(currentNode);
if (currentNode != null)
{
if (currentNode.Children != null)
{
for (int i = currentNode.Children.Count - 1; i >= 0; i--)
{
stack.Push(currentNode.Children[i]);
}
}
}
}
}
}
}
关于深度优先和广度优先,有一个博主动图做的很好:
https://blog.csdn.net/qq_44918331/article/details/115542177
深度优先--前序遍历(根左右)--动图来自上面博主的博客

广度优先--即先访问完下层所有,再往下下层访问。。。

还有一个博主写的 用的一趟for循环实现的;这个我测试了一下,是有问题的。
C# 非递归列表转树形结构的实现
测试代码
var newNodes = new List<Node>();
newNodes.Add(new Node(){Id = "1",PId = "",Data = "1节点"});
newNodes.Add(new Node(){Id = "4",PId = "2",Data = "4节点"});
newNodes.Add(new Node(){Id = "7",PId = "4",Data = "7节点"});
newNodes.Add(new Node(){Id = "5",PId = "2",Data = "5节点"});
newNodes.Add(new Node(){Id = "2",PId = "1",Data = "2节点"});
newNodes.Add(new Node(){Id = "3",PId = "1",Data = "3节点"});
var newTrees = BuildTreeV2(newNodes);
博主的代码:
public static List<Node> GetTree(List<Node> list, Func<Node, bool> IsRoot)
{
var _list = new List<Node>(list);//复制 不修改原始数据
for (int i = _list.Count() - 1; i > -1; i--)//不能使用foreach 删除或者添加元素。顺序遍历,删除元素之后,需要对当前索引执行--操作。逆序删除节点不需要特殊处理
{
Node node = _list[i];
if (!IsRoot(node))//顶级节点
{
Node pNode = _list.FirstOrDefault(a => a.Id == node.PId);//找到父节点
if (pNode != null)
{
pNode.Children.Add(node);//添加节点
}
_list.RemoveAt(i);//无论是否找到 删除,剩下的全部为顶级节点
}
}
return _list;
}


浙公网安备 33010602011771号