前言
树在图论中是一种重要的图,由于其自身的许多特殊性质,也是一种重要的计算机数据结构,在很多地方都有用。但是这些树大多都是作为其他应用的内部数据结构来使用。我们无法了解这些树的详细信息,而 .Net 也没有在内置的集合类库中提供树形数据结构的类。很多时候我们都需要树形数据完成一些工作,在自己的实践经验和查阅大量相关资料后,我编写了一个使用简单,能方便地将普通集合转换为树形集合(当然前提是这些集合元素之间存在能够表明层级关系的数据),提供了大量图论中有关节点和整棵树的信息和常用算法的通用树形数据结构类。希望能够简化此类需求的开发。
正文
图论中有关树的定义和性质都能很容易查到资料,就不再啰嗦,进入正题。首先,定义一个树形结构有很多方法,在此我选用最直观的双亲孩子表示法定义。为了能将包含层级信息的普通数据转换为树形数据,使用接口进行定义,并以接口为核心进行扩展。接口定义如下:
1 /// <summary> 2 /// 可分层数据接口 3 /// </summary> 4 /// <typeparam name="T">数据类型</typeparam> 5 public interface IHierarchical<out T> 6 { 7 /// <summary> 8 /// 当前节点的数据 9 /// </summary> 10 T Current { get; } 11 12 /// <summary> 13 /// 根节点 14 /// </summary> 15 IHierarchical<T> Root { get; } 16 17 /// <summary> 18 /// 双亲(父)节点 19 /// </summary> 20 IHierarchical<T> Parent { get; } 21 22 /// <summary> 23 /// 祖先节点集合(按路径距离升序) 24 /// </summary> 25 IEnumerable<IHierarchical<T>> Ancestors { get; } 26 27 /// <summary> 28 /// 子节点集合 29 /// </summary> 30 IEnumerable<IHierarchical<T>> Children { get; } 31 32 /// <summary> 33 /// 后代节点集合(深度优先先序遍历) 34 /// </summary> 35 IEnumerable<IHierarchical<T>> Descendants { get; } 36 37 /// <summary> 38 /// 兄弟节点集合(不包括自身节点) 39 /// </summary> 40 IEnumerable<IHierarchical<T>> Siblings { get; } 41 42 /// <summary> 43 /// 在兄弟节点中的排行 44 /// </summary> 45 int IndexOfSiblings { get; } 46 47 /// <summary> 48 /// 节点的层 49 /// </summary> 50 int Level { get; } 51 52 /// <summary> 53 /// 节点(以当前节点为根的子树)的高度 54 /// </summary> 55 int Height { get; } 56 57 /// <summary> 58 /// 节点的度 59 /// </summary> 60 int Degree { get; } 61 62 /// <summary> 63 /// 树(以当前节点为根的子树)的所有节点中度最大的节点的度 64 /// </summary> 65 int MaxDegreeOfTree { get; } 66 67 /// <summary> 68 /// 当前节点是否是根节点 69 /// </summary> 70 bool IsRoot { get; } 71 72 /// <summary> 73 /// 当前节点是否是叶子节点 74 /// </summary> 75 bool IsLeaf { get; } 76 77 /// <summary> 78 /// 当前节点是否有子节点 79 /// </summary> 80 bool HasChild { get; } 81 82 /// <summary> 83 /// 以当前节点为根返回树形排版的结构字符串 84 /// </summary> 85 /// <param name="formatter">数据对象格式化器(内容要为一行,否则会影响排版)</param> 86 /// <param name="convertToSingleLine">处理掉换行符变成单行文本</param> 87 /// <returns></returns> 88 string ToString(Func<T, string> formatter, bool convertToSingleLine = false); 89 }
然后根据接口定义扩展类型。实现如下:
1 /// <summary> 2 /// 可分层数据扩展 3 /// </summary> 4 public static class HierarchicalExtensions 5 { 6 /// <summary> 7 /// 转换为可分层数据 8 /// </summary> 9 /// <typeparam name="T">数据类型</typeparam> 10 /// <param name="item">数据</param> 11 /// <param name="childSelector">下层数据选择器</param> 12 /// <returns>已分层的数据</returns> 13 public static IHierarchical<T> AsHierarchical<T>(this T item, Func<T, IEnumerable<T>> childSelector) 14 { 15 return new Hierarchical<T>(item, childSelector); 16 } 17 18 /// <summary> 19 /// 分层数据 20 /// </summary> 21 /// <typeparam name="T">数据类型</typeparam> 22 private class Hierarchical<T> : IHierarchical<T> 23 { 24 private readonly object _locker; 25 private readonly Func<T, IEnumerable<T>> _childSelector; 26 private IEnumerable<IHierarchical<T>> _children; 27 28 /// <summary> 29 /// 实例化分层数据 30 /// </summary> 31 /// <param name="item">数据</param> 32 /// <param name="childSelector">下层数据选择器</param> 33 public Hierarchical(T item, Func<T, IEnumerable<T>> childSelector) 34 { 35 _locker = new object(); 36 _children = null; 37 Current = item; 38 _childSelector = childSelector; 39 } 40 41 /// <summary> 42 /// 实例化分层数据 43 /// </summary> 44 /// <param name="item">数据</param> 45 /// <param name="parent">上层数据</param> 46 /// <param name="childSelector">下层数据选择器</param> 47 private Hierarchical(T item, IHierarchical<T> parent, Func<T, IEnumerable<T>> childSelector) 48 : this(item, childSelector) 49 { 50 Parent = parent; 51 } 52 53 /// <summary> 54 /// 初始化下层节点集合 55 /// </summary> 56 /// <returns>迭代结果集合接口</returns> 57 private IEnumerable<IHierarchical<T>> InitializeChildren() 58 { 59 var children = _childSelector(Current); 60 if (children == null) 61 yield break; 62 63 foreach (T item in children) 64 { 65 yield return new Hierarchical<T>(item, this, _childSelector); 66 } 67 } 68 69 #region IHierarchicalDataItem<T> 成员 70 71 public T Current { get; } 72 73 public IHierarchical<T> Root 74 { 75 get 76 { 77 IHierarchical<T> node = this; 78 79 while (node.Parent != null) 80 node = node.Parent; 81 82 return node; 83 } 84 } 85 86 public IHierarchical<T> Parent { get; } 87 88 public IEnumerable<IHierarchical<T>> Ancestors 89 { 90 get 91 { 92 IHierarchical<T> node = Parent; 93 94 while (node != null) 95 { 96 yield return node; 97 node = node.Parent; 98 } 99 } 100 } 101 102 public IEnumerable<IHierarchical<T>> Children 103 { 104 get 105 { 106 if (_children == null) 107 lock (_locker) 108 if (_children == null) 109 _children = InitializeChildren().ToArray(); 110 111 return _children; 112 } 113 } 114 115 public IEnumerable<IHierarchical<T>> Descendants 116 { 117 get 118 { 119 Stack<IHierarchical<T>> stack = new Stack<IHierarchical<T>>(Children.Reverse()); 120 121 while (stack.Count > 0) 122 { 123 IHierarchical<T> node = stack.Pop(); 124 yield return node; 125 126 foreach (IHierarchical<T> child in node.Children.Reverse()) 127 { 128 stack.Push(child); 129 } 130 } 131 } 132 } 133 134 public IEnumerable<IHierarchical<T>> Siblings => Parent?.Children?.Where(node => node != this); 135 136 public int IndexOfSiblings 137 { 138 get 139 { 140 if (Parent == null) return 0; 141 142 int index = 0; 143 foreach (var child in Parent.Children) 144 { 145 if (child == this) break; 146 index++; 147 } 148 149 return index; 150 } 151 } 152 153 //无缓存方法,每次访问相同节点都会重新枚举数据源并生成结果对象 154 //包含相同数据T的包装IHierarchical<T>每次都不一样 155 //public IEnumerable<IHierarchical<T>> Children 156 //{ 157 // get 158 // { 159 // var children = _childSelector(Current); 160 // if (children == null) 161 // yield break; 162 163 // foreach (T item in children) 164 // { 165 // yield return new Hierarchical<T>(item, this, _childSelector); 166 // } 167 // } 168 //} 169 170 public int Level => Parent?.Level + 1 ?? 1; 171 172 public int Height => (Descendants.Any() ? Descendants.Select(node => node.Level).Max() - Level : 0) + 1; 173 174 public int Degree => Children?.Count() ?? 0; 175 176 public int MaxDegreeOfTree => Math.Max(Degree, Descendants.Any() ? Descendants.Select(node => node.Degree).Max() : 0); 177 178 public bool IsRoot => Parent == null; 179 180 public bool IsLeaf => Degree == 0; 181 182 public bool HasChild => !IsLeaf; 183 184 public string ToString(Func<T, string> formatter, bool convertToSingleLine = false) 185 { 186 var sbr = new StringBuilder(); 187 sbr.AppendLine(convertToSingleLine 188 ? formatter(Current).Replace(" ", @" ").Replace(" ", @" ") 189 : formatter(Current)); 190 191 var sb = new StringBuilder(); 192 foreach (var node in Descendants) 193 { 194 sb.Append(convertToSingleLine 195 ? formatter(node.Current).Replace(" ", @" ").Replace(" ", @" ") 196 : formatter(node.Current)); 197 sb.Insert(0, node == node.Parent.Children.Last() ? "└─" : "├─"); 198 199 for (int i = 0; i < node.Ancestors.Count() - (Ancestors?.Count() ?? 0) - 1; i++) 200 { 201 sb.Insert(0, 202 node.Ancestors.ElementAt(i) == node.Ancestors.ElementAt(i + 1).Children.Last() 203 ? " " 204 : "│ "); 205 } 206 207 sbr.AppendLine(sb.ToString()); 208 sb.Clear(); 209 } 210 211 return sbr.ToString(); 212 } 213 214 public override string ToString() 215 { 216 return ToString(node => node.ToString()); 217 } 218 219 #endregion 220 } 221 222 /// <summary> 223 /// 获取从根到节点的路径(中顺序经过的节点集合) 224 /// </summary> 225 /// <typeparam name="T">数据类型</typeparam> 226 /// <param name="node">节点</param> 227 /// <returns>路径中按经过顺序组成的节点集合</returns> 228 public static IEnumerable<IHierarchical<T>> GetPathFromRoot<T>(this IHierarchical<T> node) => 229 node.Ancestors.Reverse(); 230 231 /// <summary> 232 /// 判断节点是否是指定节点的祖先节点 233 /// </summary> 234 /// <typeparam name="T">节点数据类型</typeparam> 235 /// <param name="node">待判断的节点</param> 236 /// <param name="target">目标节点</param> 237 /// <returns>判断结果</returns> 238 public static bool IsAncestorOf<T>(this IHierarchical<T> node, IHierarchical<T> target) 239 { 240 if (node.Root != target.Root) 241 throw new InvalidOperationException($"{nameof(node)} and {nameof(target)} are not at same tree."); 242 243 return target.Ancestors.SingleOrDefault(n => n == node) != null; 244 } 245 246 /// <summary> 247 /// 判断节点是否是指定节点的后代节点 248 /// </summary> 249 /// <typeparam name="T">节点数据类型</typeparam> 250 /// <param name="node">待判断的节点</param> 251 /// <param name="target">目标节点</param> 252 /// <returns>判断结果</returns> 253 public static bool IsDescendantOf<T>(this IHierarchical<T> node, IHierarchical<T> target) 254 { 255 if (node.Root != target.Root) 256 throw new InvalidOperationException($"{nameof(node)} and {nameof(target)} are not at same tree."); 257 258 return target.IsAncestorOf(node); 259 } 260 261 /// <summary> 262 /// 获取节点与指定节点的最近公共祖先节点 263 /// </summary> 264 /// <typeparam name="T">节点数据类型</typeparam> 265 /// <param name="node">待查找的节点</param> 266 /// <param name="target">目标节点</param> 267 /// <returns>最近的公共祖先节点</returns> 268 public static IHierarchical<T> GetNearestCommonAncestor<T>(this IHierarchical<T> node, IHierarchical<T> target) 269 { 270 if (node.Root != target.Root) 271 throw new InvalidOperationException($"{nameof(node)} and {nameof(target)} are not at same tree."); 272 273 if (node.IsAncestorOf(target)) return node; 274 if (target.IsAncestorOf(node)) return target; 275 276 return node.Ancestors.Intersect(target.Ancestors).OrderByDescending(no => no.Level).First(); 277 } 278 279 /// <summary> 280 /// 获取从指定节点到当前节点的路径 281 /// </summary> 282 /// <typeparam name="T">节点数据类型</typeparam> 283 /// <param name="node">当前节点(终点)</param> 284 /// <param name="from">目标节点(起点)</param> 285 /// <returns>按从目标节点到当前节点顺序经过的节点集合</returns> 286 public static IEnumerable<IHierarchical<T>> GetPathFromNode<T>(this IHierarchical<T> node, 287 IHierarchical<T> from) 288 { 289 if(node.Root != from.Root) 290 throw new InvalidOperationException($"{nameof(node)} and {nameof(from)} are not at same tree."); 291 292 yield return from; 293 294 if (node == from) yield break; 295 296 if (node.IsAncestorOf(from)) 297 { 298 foreach (var ancestor in from.Ancestors) 299 { 300 yield return ancestor; 301 if (ancestor == node) 302 { 303 yield break; 304 } 305 } 306 } 307 308 var ancestorsOfNode = node.Ancestors.ToArray(); 309 if (node.IsDescendantOf(from)) 310 { 311 for (int i = Array.IndexOf(ancestorsOfNode, from) - 1; i >= 0; i--) 312 { 313 yield return ancestorsOfNode[i]; 314 } 315 316 yield return node; 317 yield break; 318 } 319 320 var keyNode = ancestorsOfNode.Intersect(from.Ancestors).OrderByDescending(no => no.Level).First(); 321 foreach (var ancestor in from.Ancestors) 322 { 323 yield return ancestor; 324 if (ancestor == keyNode) 325 { 326 break; 327 } 328 } 329 330 for (int i = Array.IndexOf(ancestorsOfNode, keyNode) - 1; i >= 0; i--) 331 { 332 yield return ancestorsOfNode[i]; 333 } 334 335 yield return node; 336 } 337 338 /// <summary> 339 /// 获取从当前节点到指定节点的路径 340 /// </summary> 341 /// <typeparam name="T">节点数据类型</typeparam> 342 /// <param name="node">当前节点(起点)</param> 343 /// <param name="to">目标节点(终点)</param> 344 /// <returns>按从当前节点到目标节点顺序经过的节点集合</returns> 345 public static IEnumerable<IHierarchical<T>> GetPathToNode<T>(this IHierarchical<T> node, IHierarchical<T> to) 346 { 347 if (node.Root != to.Root) 348 throw new InvalidOperationException($"{nameof(node)} and {nameof(to)} are not at same tree."); 349 350 return to.GetPathFromNode(node); 351 } 352 353 /// <summary> 354 /// 获取子孙数据(深度优先,先序) 355 /// </summary> 356 /// <typeparam name="T">数据类型</typeparam> 357 /// <param name="root">根</param> 358 /// <param name="predicate">子孙筛选条件</param> 359 /// <returns>筛选的子孙</returns> 360 public static IEnumerable<IHierarchical<T>> GetDescendantsDfsDlr<T>(this IHierarchical<T> root, 361 Func<IHierarchical<T>, bool> predicate = null) 362 { 363 var children = predicate == null ? root.Children : root.Children.Where(predicate); 364 Stack<IHierarchical<T>> stack = new Stack<IHierarchical<T>>(children.Reverse()); 365 366 while (stack.Count > 0) 367 { 368 IHierarchical<T> node = stack.Pop(); 369 yield return node; 370 371 children = predicate == null ? node.Children : node.Children.Where(predicate); 372 foreach (IHierarchical<T> child in children.Reverse()) 373 { 374 stack.Push(child); 375 } 376 } 377 378 #region 递归方式 379 380 //foreach (T t in childSelector(root)) 381 //{ 382 // if (predicate(t)) 383 // yield return t; 384 // foreach (T child in GetDescendantDfsDlr(t, childSelector, predicate)) 385 // yield return child; 386 //} 387 388 #endregion 递归方式 389 } 390 391 /// <summary> 392 /// 获取子孙数据(深度优先,后序) 393 /// </summary> 394 /// <typeparam name="T">数据类型</typeparam> 395 /// <param name="root">根</param> 396 /// <param name="predicate">子孙筛选条件</param> 397 /// <returns>筛选的子孙</returns> 398 public static IEnumerable<IHierarchical<T>> GetDescendantsDfsLrd<T>(this IHierarchical<T> root, 399 Func<IHierarchical<T>, bool> predicate = null) 400 { 401 var children = predicate == null ? root.Children : root.Children.Where(predicate); 402 Stack<IHierarchical<T>> stack = new Stack<IHierarchical<T>>(children.Reverse()); 403 404 IHierarchical<T> lastAccessedNode = null; 405 406 while (stack.Count > 0) 407 { 408 var node = stack.Peek(); 409 410 if (node.Children.Any() && node.Children.Last() != lastAccessedNode) 411 { 412 children = predicate == null ? node.Children : node.Children.Where(predicate); 413 foreach (IHierarchical<T> child in children.Reverse()) 414 { 415 stack.Push(child); 416 } 417 } 418 else 419 { 420 yield return node; 421 422 lastAccessedNode = node; 423 stack.Pop(); 424 } 425 } 426 } 427 428 /// <summary> 429 /// 获取子孙数据(广度优先) 430 /// </summary> 431 /// <typeparam name="T">数据类型</typeparam> 432 /// <param name="root">根</param> 433 /// <param name="predicate">子孙筛选条件</param> 434 /// <returns>筛选的子孙</returns> 435 public static IEnumerable<IHierarchical<T>> GetDescendantsBfs<T>(this IHierarchical<T> root, 436 Func<IHierarchical<T>, bool> predicate = null) 437 { 438 predicate = predicate ?? (t => true); 439 Queue<IHierarchical<T>> queue = new Queue<IHierarchical<T>>(root.Children.Where(predicate)); 440 441 while (queue.Count > 0) 442 { 443 IHierarchical<T> node = queue.Dequeue(); 444 yield return node; 445 446 foreach (IHierarchical<T> child in node.Children.Where(predicate)) 447 { 448 queue.Enqueue(child); 449 } 450 } 451 } 452 453 /// <summary> 454 /// 转换为可枚举集合 455 /// </summary> 456 /// <typeparam name="T">数据类型</typeparam> 457 /// <param name="root">根</param> 458 /// <param name="predicate">子孙筛选条件</param> 459 /// <param name="enumerateType">枚举方式</param> 460 /// <returns>已枚举的集合</returns> 461 public static IEnumerable<IHierarchical<T>> AsEnumerable<T>(this IHierarchical<T> root, 462 EnumerateType enumerateType = EnumerateType.DfsDlr, 463 Func<IHierarchical<T>, bool> predicate = null) 464 { 465 switch (enumerateType) 466 { 467 case EnumerateType.DfsDlr: 468 yield return root; 469 470 foreach (var descendant in GetDescendantsDfsDlr(root, predicate)) 471 { 472 yield return descendant; 473 } 474 475 break; 476 case EnumerateType.DfsLrd: 477 foreach (var descendant in GetDescendantsDfsLrd(root, predicate)) 478 { 479 yield return descendant; 480 } 481 482 yield return root; 483 484 break; 485 case EnumerateType.Bfs: 486 yield return root; 487 488 foreach (var descendant in GetDescendantsBfs(root, predicate)) 489 { 490 yield return descendant; 491 } 492 493 break; 494 default: 495 throw new ArgumentOutOfRangeException(nameof(enumerateType), enumerateType, null); 496 } 497 } 498 }
里面需要用到一个枚举,定义如下:
1 /// <summary> 2 /// 枚举方式 3 /// </summary> 4 public enum EnumerateType : byte 5 { 6 /// <summary> 7 /// 深度优先,先序 8 /// </summary> 9 DfsDlr, 10 11 /// <summary> 12 /// 深度优先,后序 13 /// </summary> 14 DfsLrd, 15 16 /// <summary> 17 /// 广度优先(层序) 18 /// </summary> 19 Bfs 20 }
这个实现类是一个不可变的树形数据类,一旦生成,无法修改,主要是作为普通数据的转换包装使用。但是其中的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一下。