C# 通用樹形數據結構

前言

    樹在圖論中是一種重要的圖,由於其自身的許多特殊性質,也是一種重要的計算機數據結構,在很多地方都有用。但是這些樹大多都是作爲其他應用的內部數據結構來使用。我們無法瞭解這些樹的詳細信息,而 .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("\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     }

    裏面需要用到一個枚舉,定義如下:

 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一下。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章