前言

树在图论中是一种重要的图,由于其自身的许多特殊性质,也是一种重要的计算机数据结构,在很多地方都有用。但是这些树大多都是作为其他应用的内部数据结构来使用。我们无法了解这些树的详细信息,而 .Net 也没有在内置的集合类库中提供树形数据结构的类。很多时候我们都需要树形数据完成一些工作,在自己的实践经验和查阅大量相关资料后,我编写了一个使用简单,能方便地将普通集合转换为树形集合(当然前提是这些集合元素之间存在能够表明层级关系的数据),提供了大量图论中有关节点和整棵树的信息和常用算法的通用树形数据结构类。希望能够简化此类需求的开发。

正文

图论中有关树的定义和性质都能很容易查到资料,就不再啰嗦,进入正题。首先,定义一个树形结构有很多方法,在此我选用最直观的双亲孩子表示法定义。为了能将包含层级信息的普通数据转换为树形数据,使用接口进行定义,并以接口为核心进行扩展。接口定义如下:

     /// <summary>
/// 可分层数据接口
/// </summary>
/// <typeparam name="T">数据类型</typeparam>
public interface IHierarchical<out T>
{
/// <summary>
/// 当前节点的数据
/// </summary>
T Current { get; } /// <summary>
/// 根节点
/// </summary>
IHierarchical<T> Root { get; } /// <summary>
/// 双亲(父)节点
/// </summary>
IHierarchical<T> Parent { get; } /// <summary>
/// 祖先节点集合(按路径距离升序)
/// </summary>
IEnumerable<IHierarchical<T>> Ancestors { get; } /// <summary>
/// 子节点集合
/// </summary>
IEnumerable<IHierarchical<T>> Children { get; } /// <summary>
/// 后代节点集合(深度优先先序遍历)
/// </summary>
IEnumerable<IHierarchical<T>> Descendants { get; } /// <summary>
/// 兄弟节点集合(不包括自身节点)
/// </summary>
IEnumerable<IHierarchical<T>> Siblings { get; } /// <summary>
/// 在兄弟节点中的排行
/// </summary>
int IndexOfSiblings { get; } /// <summary>
/// 节点的层
/// </summary>
int Level { get; } /// <summary>
/// 节点(以当前节点为根的子树)的高度
/// </summary>
int Height { get; } /// <summary>
/// 节点的度
/// </summary>
int Degree { get; } /// <summary>
/// 树(以当前节点为根的子树)的所有节点中度最大的节点的度
/// </summary>
int MaxDegreeOfTree { get; } /// <summary>
/// 当前节点是否是根节点
/// </summary>
bool IsRoot { get; } /// <summary>
/// 当前节点是否是叶子节点
/// </summary>
bool IsLeaf { get; } /// <summary>
/// 当前节点是否有子节点
/// </summary>
bool HasChild { get; } /// <summary>
/// 以当前节点为根返回树形排版的结构字符串
/// </summary>
/// <param name="formatter">数据对象格式化器(内容要为一行,否则会影响排版)</param>
/// <param name="convertToSingleLine">处理掉换行符变成单行文本</param>
/// <returns></returns>
string ToString(Func<T, string> formatter, bool convertToSingleLine = false);
}

然后根据接口定义扩展类型。实现如下:

     /// <summary>
/// 可分层数据扩展
/// </summary>
public static class HierarchicalExtensions
{
/// <summary>
/// 转换为可分层数据
/// </summary>
/// <typeparam name="T">数据类型</typeparam>
/// <param name="item">数据</param>
/// <param name="childSelector">下层数据选择器</param>
/// <returns>已分层的数据</returns>
public static IHierarchical<T> AsHierarchical<T>(this T item, Func<T, IEnumerable<T>> childSelector)
{
return new Hierarchical<T>(item, childSelector);
} /// <summary>
/// 分层数据
/// </summary>
/// <typeparam name="T">数据类型</typeparam>
private class Hierarchical<T> : IHierarchical<T>
{
private readonly object _locker;
private readonly Func<T, IEnumerable<T>> _childSelector;
private IEnumerable<IHierarchical<T>> _children; /// <summary>
/// 实例化分层数据
/// </summary>
/// <param name="item">数据</param>
/// <param name="childSelector">下层数据选择器</param>
public Hierarchical(T item, Func<T, IEnumerable<T>> childSelector)
{
_locker = new object();
_children = null;
Current = item;
_childSelector = childSelector;
} /// <summary>
/// 实例化分层数据
/// </summary>
/// <param name="item">数据</param>
/// <param name="parent">上层数据</param>
/// <param name="childSelector">下层数据选择器</param>
private Hierarchical(T item, IHierarchical<T> parent, Func<T, IEnumerable<T>> childSelector)
: this(item, childSelector)
{
Parent = parent;
} /// <summary>
/// 初始化下层节点集合
/// </summary>
/// <returns>迭代结果集合接口</returns>
private IEnumerable<IHierarchical<T>> InitializeChildren()
{
var children = _childSelector(Current);
if (children == null)
yield break; foreach (T item in children)
{
yield return new Hierarchical<T>(item, this, _childSelector);
}
} #region IHierarchicalDataItem<T> 成员 public T Current { get; } public IHierarchical<T> Root
{
get
{
IHierarchical<T> node = this; while (node.Parent != null)
node = node.Parent; return node;
}
} public IHierarchical<T> Parent { get; } public IEnumerable<IHierarchical<T>> Ancestors
{
get
{
IHierarchical<T> node = Parent; while (node != null)
{
yield return node;
node = node.Parent;
}
}
} public IEnumerable<IHierarchical<T>> Children
{
get
{
if (_children == null)
lock (_locker)
if (_children == null)
_children = InitializeChildren().ToArray(); return _children;
}
} public IEnumerable<IHierarchical<T>> Descendants
{
get
{
Stack<IHierarchical<T>> stack = new Stack<IHierarchical<T>>(Children.Reverse()); while (stack.Count > )
{
IHierarchical<T> node = stack.Pop();
yield return node; foreach (IHierarchical<T> child in node.Children.Reverse())
{
stack.Push(child);
}
}
}
} public IEnumerable<IHierarchical<T>> Siblings => Parent?.Children?.Where(node => node != this); public int IndexOfSiblings
{
get
{
if (Parent == null) return ; int index = ;
foreach (var child in Parent.Children)
{
if (child == this) break;
index++;
} return index;
}
} //无缓存方法,每次访问相同节点都会重新枚举数据源并生成结果对象
//包含相同数据T的包装IHierarchical<T>每次都不一样
//public IEnumerable<IHierarchical<T>> Children
//{
// get
// {
// var children = _childSelector(Current);
// if (children == null)
// yield break; // foreach (T item in children)
// {
// yield return new Hierarchical<T>(item, this, _childSelector);
// }
// }
//} public int Level => Parent?.Level + ?? ; public int Height => (Descendants.Any() ? Descendants.Select(node => node.Level).Max() - Level : ) + ; public int Degree => Children?.Count() ?? ; public int MaxDegreeOfTree => Math.Max(Degree, Descendants.Any() ? Descendants.Select(node => node.Degree).Max() : ); public bool IsRoot => Parent == null; public bool IsLeaf => Degree == ; public bool HasChild => !IsLeaf; public string ToString(Func<T, string> formatter, bool convertToSingleLine = false)
{
var sbr = new StringBuilder();
sbr.AppendLine(convertToSingleLine
? formatter(Current).Replace("\r", @"\r").Replace("\n", @"\n")
: formatter(Current)); var sb = new StringBuilder();
foreach (var node in Descendants)
{
sb.Append(convertToSingleLine
? formatter(node.Current).Replace("\r", @"\r").Replace("\n", @"\n")
: formatter(node.Current));
sb.Insert(, node == node.Parent.Children.Last() ? "└─" : "├─"); for (int i = ; i < node.Ancestors.Count() - (Ancestors?.Count() ?? ) - ; i++)
{
sb.Insert(,
node.Ancestors.ElementAt(i) == node.Ancestors.ElementAt(i + ).Children.Last()
? " "
: "│ ");
} sbr.AppendLine(sb.ToString());
sb.Clear();
} return sbr.ToString();
} public override string ToString()
{
return ToString(node => node.ToString());
} #endregion
} /// <summary>
/// 获取从根到节点的路径(中顺序经过的节点集合)
/// </summary>
/// <typeparam name="T">数据类型</typeparam>
/// <param name="node">节点</param>
/// <returns>路径中按经过顺序组成的节点集合</returns>
public static IEnumerable<IHierarchical<T>> GetPathFromRoot<T>(this IHierarchical<T> node) =>
node.Ancestors.Reverse(); /// <summary>
/// 判断节点是否是指定节点的祖先节点
/// </summary>
/// <typeparam name="T">节点数据类型</typeparam>
/// <param name="node">待判断的节点</param>
/// <param name="target">目标节点</param>
/// <returns>判断结果</returns>
public static bool IsAncestorOf<T>(this IHierarchical<T> node, IHierarchical<T> target)
{
if (node.Root != target.Root)
throw new InvalidOperationException($"{nameof(node)} and {nameof(target)} are not at same tree."); return target.Ancestors.SingleOrDefault(n => n == node) != null;
} /// <summary>
/// 判断节点是否是指定节点的后代节点
/// </summary>
/// <typeparam name="T">节点数据类型</typeparam>
/// <param name="node">待判断的节点</param>
/// <param name="target">目标节点</param>
/// <returns>判断结果</returns>
public static bool IsDescendantOf<T>(this IHierarchical<T> node, IHierarchical<T> target)
{
if (node.Root != target.Root)
throw new InvalidOperationException($"{nameof(node)} and {nameof(target)} are not at same tree."); return target.IsAncestorOf(node);
} /// <summary>
/// 获取节点与指定节点的最近公共祖先节点
/// </summary>
/// <typeparam name="T">节点数据类型</typeparam>
/// <param name="node">待查找的节点</param>
/// <param name="target">目标节点</param>
/// <returns>最近的公共祖先节点</returns>
public static IHierarchical<T> GetNearestCommonAncestor<T>(this IHierarchical<T> node, IHierarchical<T> target)
{
if (node.Root != target.Root)
throw new InvalidOperationException($"{nameof(node)} and {nameof(target)} are not at same tree."); if (node.IsAncestorOf(target)) return node;
if (target.IsAncestorOf(node)) return target; return node.Ancestors.Intersect(target.Ancestors).OrderByDescending(no => no.Level).First();
} /// <summary>
/// 获取从指定节点到当前节点的路径
/// </summary>
/// <typeparam name="T">节点数据类型</typeparam>
/// <param name="node">当前节点(终点)</param>
/// <param name="from">目标节点(起点)</param>
/// <returns>按从目标节点到当前节点顺序经过的节点集合</returns>
public static IEnumerable<IHierarchical<T>> GetPathFromNode<T>(this IHierarchical<T> node,
IHierarchical<T> from)
{
if(node.Root != from.Root)
throw new InvalidOperationException($"{nameof(node)} and {nameof(from)} are not at same tree."); yield return from; if (node == from) yield break; if (node.IsAncestorOf(from))
{
foreach (var ancestor in from.Ancestors)
{
yield return ancestor;
if (ancestor == node)
{
yield break;
}
}
} var ancestorsOfNode = node.Ancestors.ToArray();
if (node.IsDescendantOf(from))
{
for (int i = Array.IndexOf(ancestorsOfNode, from) - ; i >= ; i--)
{
yield return ancestorsOfNode[i];
} yield return node;
yield break;
} var keyNode = ancestorsOfNode.Intersect(from.Ancestors).OrderByDescending(no => no.Level).First();
foreach (var ancestor in from.Ancestors)
{
yield return ancestor;
if (ancestor == keyNode)
{
break;
}
} for (int i = Array.IndexOf(ancestorsOfNode, keyNode) - ; i >= ; i--)
{
yield return ancestorsOfNode[i];
} yield return node;
} /// <summary>
/// 获取从当前节点到指定节点的路径
/// </summary>
/// <typeparam name="T">节点数据类型</typeparam>
/// <param name="node">当前节点(起点)</param>
/// <param name="to">目标节点(终点)</param>
/// <returns>按从当前节点到目标节点顺序经过的节点集合</returns>
public static IEnumerable<IHierarchical<T>> GetPathToNode<T>(this IHierarchical<T> node, IHierarchical<T> to)
{
if (node.Root != to.Root)
throw new InvalidOperationException($"{nameof(node)} and {nameof(to)} are not at same tree."); return to.GetPathFromNode(node);
} /// <summary>
/// 获取子孙数据(深度优先,先序)
/// </summary>
/// <typeparam name="T">数据类型</typeparam>
/// <param name="root">根</param>
/// <param name="predicate">子孙筛选条件</param>
/// <returns>筛选的子孙</returns>
public static IEnumerable<IHierarchical<T>> GetDescendantsDfsDlr<T>(this IHierarchical<T> root,
Func<IHierarchical<T>, bool> predicate = null)
{
var children = predicate == null ? root.Children : root.Children.Where(predicate);
Stack<IHierarchical<T>> stack = new Stack<IHierarchical<T>>(children.Reverse()); while (stack.Count > )
{
IHierarchical<T> node = stack.Pop();
yield return node; children = predicate == null ? node.Children : node.Children.Where(predicate);
foreach (IHierarchical<T> child in children.Reverse())
{
stack.Push(child);
}
} #region 递归方式 //foreach (T t in childSelector(root))
//{
// if (predicate(t))
// yield return t;
// foreach (T child in GetDescendantDfsDlr(t, childSelector, predicate))
// yield return child;
//} #endregion 递归方式
} /// <summary>
/// 获取子孙数据(深度优先,后序)
/// </summary>
/// <typeparam name="T">数据类型</typeparam>
/// <param name="root">根</param>
/// <param name="predicate">子孙筛选条件</param>
/// <returns>筛选的子孙</returns>
public static IEnumerable<IHierarchical<T>> GetDescendantsDfsLrd<T>(this IHierarchical<T> root,
Func<IHierarchical<T>, bool> predicate = null)
{
var children = predicate == null ? root.Children : root.Children.Where(predicate);
Stack<IHierarchical<T>> stack = new Stack<IHierarchical<T>>(children.Reverse()); IHierarchical<T> lastAccessedNode = null; while (stack.Count > )
{
var node = stack.Peek(); if (node.Children.Any() && node.Children.Last() != lastAccessedNode)
{
children = predicate == null ? node.Children : node.Children.Where(predicate);
foreach (IHierarchical<T> child in children.Reverse())
{
stack.Push(child);
}
}
else
{
yield return node; lastAccessedNode = node;
stack.Pop();
}
}
} /// <summary>
/// 获取子孙数据(广度优先)
/// </summary>
/// <typeparam name="T">数据类型</typeparam>
/// <param name="root">根</param>
/// <param name="predicate">子孙筛选条件</param>
/// <returns>筛选的子孙</returns>
public static IEnumerable<IHierarchical<T>> GetDescendantsBfs<T>(this IHierarchical<T> root,
Func<IHierarchical<T>, bool> predicate = null)
{
predicate = predicate ?? (t => true);
Queue<IHierarchical<T>> queue = new Queue<IHierarchical<T>>(root.Children.Where(predicate)); while (queue.Count > )
{
IHierarchical<T> node = queue.Dequeue();
yield return node; foreach (IHierarchical<T> child in node.Children.Where(predicate))
{
queue.Enqueue(child);
}
}
} /// <summary>
/// 转换为可枚举集合
/// </summary>
/// <typeparam name="T">数据类型</typeparam>
/// <param name="root">根</param>
/// <param name="predicate">子孙筛选条件</param>
/// <param name="enumerateType">枚举方式</param>
/// <returns>已枚举的集合</returns>
public static IEnumerable<IHierarchical<T>> AsEnumerable<T>(this IHierarchical<T> root,
EnumerateType enumerateType = EnumerateType.DfsDlr,
Func<IHierarchical<T>, bool> predicate = null)
{
switch (enumerateType)
{
case EnumerateType.DfsDlr:
yield return root; foreach (var descendant in GetDescendantsDfsDlr(root, predicate))
{
yield return descendant;
} break;
case EnumerateType.DfsLrd:
foreach (var descendant in GetDescendantsDfsLrd(root, predicate))
{
yield return descendant;
} yield return root; break;
case EnumerateType.Bfs:
yield return root; foreach (var descendant in GetDescendantsBfs(root, predicate))
{
yield return descendant;
} break;
default:
throw new ArgumentOutOfRangeException(nameof(enumerateType), enumerateType, null);
}
}
}

里面需要用到一个枚举,定义如下:

     /// <summary>
/// 枚举方式
/// </summary>
public enum EnumerateType : byte
{
/// <summary>
/// 深度优先,先序
/// </summary>
DfsDlr, /// <summary>
/// 深度优先,后序
/// </summary>
DfsLrd, /// <summary>
/// 广度优先(层序)
/// </summary>
Bfs
}

这个实现类是一个不可变的树形数据类,一旦生成,无法修改,主要是作为普通数据的转换包装使用。但是其中的Children属性是延迟加载的,只有第一次访问Children属性或其他直接或间接依赖Children属性的成员时才会初始化Children属性,所以如果在访问到Children之前修改了源数据集,修改是会反映到Children中的。这个实现的遍历算法都采用了循环法,而非递归法,能有效避免栈溢出错误,运行效率也更好。

使用public static IHierarchical<T> AsHierarchical<T>(this T item, Func<T, IEnumerable<T>> childSelector)扩展方法就可以获取根节点的引用。其中childSelector是一个委托,用于获取源数据集中每个节点的子节点集合。

GetDescendants系列扩展方法可以以指定的遍历方式获取某个节点后代节点(不包括自身节点),并且其中的Func<IHierarchical<T>, bool> predicate参数可以对后代节点进行筛选,只有符合条件的才会返回,类似Linq中的Where方法,不过要注意,如果某个节点不符合条件,那么那个节点的后代也会被过滤掉。

AsEnumerable方法和GetDescendants系列方法差不多,不过这个方法会遍历包括自身在内的整棵树,这个方法同样可以进行过滤,注意点也和GetDescendants系列方法一样。

GetPath系列扩展方法可以对同一颗树的两个节点进行路径查找,返回的集合按顺序是从起点到终点所经过的所有节点,包括起点和终点。

IsXxxOf系列扩展方法可以判断同一颗树的两个节点是否存在祖先后代关系。

GetNearestCommonAncestor扩展方法可以查找同一颗树两个节点的最近公共祖先节点,如果两个节点间存在祖先后代关系则返回祖先节点。

string ToString(Func<T, string> formatter, bool convertToSingleLine = false)接口方法可以生成类似 Windows cmd 命令 tree 生成的目录树样式的字符串,方便总览树的结构。Func<T, string> formatter委托用于获取数据T的字符串表示形式, bool convertToSingleLine 用于指定是否要强行把获得的字符串中的换行符处理掉,避免排版混乱。

结语

至此,一个通用的树形结构类就大功告成,图论中有关树的信息和常用算法基本上已经包括在内。数据集只要逻辑上包含层次关系就可以轻松包装成树形数据,除了不能修改这棵树这个小问题。自定义实现 IHierarchical<out T> 接口的类可以实现包含特殊功能的树形数据类。如果想起来能增加什么功能的话,会在Github上更新代码。

转载请完整保留以下内容,未经授权删除以下内容进行转载盗用的,保留追究法律责任的权利!

本文地址:https://www.cnblogs.com/coredx/p/10517448.html

完整源代码(包含示例):Github

里面有各种小东西,这只是其中之一,不嫌弃的话可以Star一下。

C# 通用树形数据结构的更多相关文章

  1. SQLServer树形数据结构的数据进行数据统计

    前言 前几天朋友问我,关于SQLServer数据库中对树形结构的表数据统计问题,需求大致如下: 分类表(递归数据),A的子分类是B,B的子分类是C--分类关系不间断,A为第一层,B为第二层,C为第三层 ...

  2. js treeData 树形数据结构 无限层级(转载)

    js实现无限层级树形数据结构(创新算法) 转载:https://blog.csdn.net/Mr_JavaScript/article/details/82817177 由于做项目的需要,把一个线性数 ...

  3. [SinGuLaRiTy] (树形)数据结构题目复习

    [SinGuLaRiTy-1023] Copyright (c) SinGuLaRiTy 2017. All Rights Reserved. 普通平衡树 题目描述 你需要写一种数据结构(可参考题目标 ...

  4. C#构建树形数据结构

    转自:https://www.jb51.net/article/125747.htm 树形结构:最近在做任务管理,任务可以无限派生子任务且没有数量限制,前端采用Easyui的Treegrid树形展示控 ...

  5. js树形数据结构的扁平化

    前面我们封装了一维数组(具备树形结构相关属性)处理成树形结构的方法:https://www.cnblogs.com/coder--wang/p/15013664.html 接下来我们来一波反向操作,封 ...

  6. 【Luogu】【关卡2-14】 树形数据结构(2017年10月)【AK】

    任务说明:由一个根节点分叉,越分越多,就成了树.树可以表示数据之间的从属关系 P1087 FBI树 给一个01字符串,0对应B,1对应I,F对应既有0子节点又有1子节点的根节点,输出这棵树的后序遍历. ...

  7. VUE实现Studio管理后台(七):树形结构,文件树,节点树共用一套代码NodeTree

    本次介绍的内容,稍稍复杂了一点,用VUE实现树形结构.目前这个属性结构还没有编辑功能,仅仅是展示.明天再开一篇文章,介绍如何增加编辑功能,标题都想好了.先看今天的展示效果: 构建树必须用到递归,使用s ...

  8. [数据结构]——二叉树(Binary Tree)、二叉搜索树(Binary Search Tree)及其衍生算法

    二叉树(Binary Tree)是最简单的树形数据结构,然而却十分精妙.其衍生出各种算法,以致于占据了数据结构的半壁江山.STL中大名顶顶的关联容器--集合(set).映射(map)便是使用二叉树实现 ...

  9. 算法手记 之 数据结构(线段树详解)(POJ 3468)

    依然延续第一篇读书笔记,这一篇是基于<ACM/ICPC 算法训练教程>上关于线段树的讲解的总结和修改(这本书在线段树这里Error非常多),但是总体来说这本书关于具体算法的讲解和案例都是不 ...

随机推荐

  1. 利用Gson将JSON数据进行格式化(pretty print)

    我们可以利用Gson包将String类型的JSON数据进行格式化. Gson gson = new GsonBuilder().setPrettyPrinting().create(); JsonPa ...

  2. JavaScript基础进阶之数组方法总结

    数组常用方法总结:  下面我只总结了es3中常用的数组方法,一共有11个.es5中新增的9个数组方法,后续再单独总结. 1个连接数组的方法:concat() 2个数组转换为字符串的方法:join(). ...

  3. JS传递函数并且调用

    封装的函数: function getDataByJsonP(methName, inData, fn) { // 这里fn可以直接传入函数名字 $.ajax({ url: '', //请求的url地 ...

  4. 10条Linux 命令了解服务器当前性能

    参考:http://www.infoq.com/cn/news/2015/12/linux-performance 1. uptime 如果电脑运行缓慢,执行 uptime 可以大致查看Linux服务 ...

  5. 1562. [NOI2009]变换序列【二分图】

    Description Input Output Sample Input 5 1 1 2 2 1 Sample Output 1 2 4 0 3 HINT 30%的数据中N≤50: 60%的数据中N ...

  6. 1001. [BJOI2006]狼抓兔子【最小割】

    Description 现在小朋友们最喜欢的"喜羊羊与灰太狼",话说灰太狼抓羊不到,但抓兔子还是比较在行的, 而且现在的兔子还比较笨,它们只有两个窝,现在你做为狼王,面对下面这样一 ...

  7. Codeforces 1106 E. Lunar New Year and Red Envelopes 优先队列+dp

    题意大致是Bob新年拿红包,每个红包可以在s-t时间内取,但是取了之后得在d+1时间开始才能继续取红包. 同时他女儿能在m个时间点阻止他取红包,求女儿阻止后Bob取得的w总和最小值. Bob取红包的策 ...

  8. 最简单的php学习

    php文件操作函数 filetype()判断文件的基本类型 egg 目录文件  文件  等  dir文件夹 file 文件 stat()函数获得指定文件名参数目标文件的基本属性 在php中以is_开头 ...

  9. Windows下docker的安装,将ASP.NET Core程序部署在Linux和Docker中

    参考文章: https://www.cnblogs.com/jRoger/p/aspnet-core-deploy-to-docker.html docker for windows下载连接: htt ...

  10. cpu 基础知识

    认识cpu(中央处理器简称处理器)也叫CPU,Central Processing Unit线程是安排CPU执行的最小单位 四核八线程内涵: 每个单位时间内,一个CPU只能处理一个线程(操作系统:th ...