樹在圖論中是一種重要的圖,因爲其自身的許多特殊性質,也是一種重要的計算機數據結構,在不少地方都有用。可是這些樹大多都是做爲其餘應用的內部數據結構來使用。咱們沒法瞭解這些樹的詳細信息,而 .Net 也沒有在內置的集合類庫中提供樹形數據結構的類。不少時候咱們都須要樹形數據完成一些工做,在本身的實踐經驗和查閱大量相關資料後,我編寫了一個使用簡單,能方便地將普通集合轉換爲樹形集合(固然前提是這些集合元素之間存在可以代表層級關係的數據),提供了大量圖論中有關節點和整棵樹的信息和經常使用算法的通用樹形數據結構類。但願可以簡化此類需求的開發。html
圖論中有關樹的定義和性質都能很容易查到資料,就再也不囉嗦,進入正題。首先,定義一個樹形結構有不少方法,在此我選用最直觀的雙親孩子表示法定義。爲了能將包含層級信息的普通數據轉換爲樹形數據,使用接口進行定義,並以接口爲核心進行擴展。接口定義以下:node
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 }
而後根據接口定義擴展類型。實現以下:git
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("\r", @"\r").Replace("\n", @"\n") 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("\r", @"\r").Replace("\n", @"\n") 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 }
裏面須要用到一個枚舉,定義以下:github
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系列方法同樣。ide
GetPath系列擴展方法能夠對同一顆樹的兩個節點進行路徑查找,返回的集合按順序是從起點到終點所通過的全部節點,包括起點和終點。ui
IsXxxOf系列擴展方法能夠判斷同一顆樹的兩個節點是否存在祖前後代關係。this
GetNearestCommonAncestor擴展方法能夠查找同一顆樹兩個節點的最近公共祖先節點,若是兩個節點間存在祖前後代關係則返回祖先節點。
string ToString(Func<T, string> formatter, bool convertToSingleLine = false)接口方法能夠生成相似 Windows cmd 命令 tree 生成的目錄樹樣式的字符串,方便總覽樹的結構。Func<T, string> formatter委託用於獲取數據T的字符串表示形式, bool convertToSingleLine 用於指定是否要強行把得到的字符串中的換行符處理掉,避免排版混亂。
至此,一個通用的樹形結構類就大功告成,圖論中有關樹的信息和經常使用算法基本上已經包括在內。數據集只要邏輯上包含層次關係就能夠輕鬆包裝成樹形數據,除了不能修改這棵樹這個小問題。自定義實現 IHierarchical<out T> 接口的類能夠實現包含特殊功能的樹形數據類。若是想起來能增長什麼功能的話,會在Github上更新代碼。
本文地址:http://www.javashuo.com/article/p-cebraxvg-dg.html
完整源代碼(包含示例):Github
裏面有各類小東西,這只是其中之一,不嫌棄的話能夠Star一下。