算法:圖(Graph)的遍歷、最小生成樹和拓撲排序


背景

不同的數據結構有不同的用途,像:數組、鏈表、隊列、棧多數是用來做為基本的工具使用,二叉樹多用來作為已排序元素列表的存儲,B 樹用在存儲中,本文介紹的 Graph 多數是為了解決現實問題(說到底,所有的數據結構都是這個目的),如:網絡布局、任務安排等。

圖的基本概念

示例

頂點(Vertex)

上圖的 1、2、3、4、5、6 就是頂點。

鄰接(Adjoin)

如果 A 和 B 通過定向邊相連,且方向為 A -> B,則 B 為 A 的鄰接,如果相連的邊是沒有方向的,則 A 和 B 互為鄰接。

邊(Edge)

頂點之間的連線就是邊。

連通圖(Connected Graph)

不考慮邊的方向性,從任何一個節點都可以遍歷到其它節點。本文的所有算法(除了拓撲排序)只適合連通圖。

定向圖(Directed Graph)

圖中的所有邊都帶方向。

權重圖(Weighted Graph)

圖中的所有邊都有權重。

圖的程序表示

如果圖 T 有 A、B、C 三個節點,有 A -> B,B -> C,C -> A 三個邊(沒有方向),如何在程序中表示 T 呢?

鄰接矩陣

    A   B   C
A  0   1   0
B  0   0   1
C  1   0   0

矩陣的行代表了定向邊的起始頂點,矩陣的列代表了定向邊的介紹頂點,矩陣中的值:1 代表了列是行的鄰接,0 則反之。如果邊沒有方向,則矩陣是相對於正對角線是對稱的。

鄰接列表

A:[ B ]
B:[ C ]
C:[ A ]

這個就不多說了,多數書籍使用都是鄰接矩陣。

代碼

 1         class Vertex<T>
 2         {
 3             public T Value { get; set; }
 4 
 5             public bool WasVisited { get; set; }
 6         }
 7 
 8         class Graph<T>
 9         {
10             #region 私有字段
11 
12             private int _maxSize;
13             private Vertex<T>[] _vertexs;
14             private bool[][] _edges;
15             private int _vertexCount = 0;
16 
17             #endregion
18 
19             #region 構造方法
20 
21             public Graph(int maxSize)
22             {
23                 _maxSize = maxSize;
24                 _vertexs = new Vertex<T>[_maxSize];
25                 _edges = new bool[_maxSize][];
26                 for (var i = 0; i < _maxSize; i++)
27                 {
28                     _edges[i] = new bool[_maxSize];
29                 }
30             }
31 
32             #endregion
33 
34             #region 添加頂點
35 
36             public Graph<T> AddVertex(T value)
37             {
38                 _vertexs[_vertexCount++] = new Vertex<T> { Value = value };
39 
40                 return this;
41             }
42 
43             #endregion
44 
45             #region 添加邊
46 
47             public Graph<T> AddUnDirectedEdge(T startItem, T endItem)
48             {
49                 var startIndex = this.GetVertexIndex(startItem);
50                 var endIndex = this.GetVertexIndex(endItem);
51 
52                 _edges[startIndex][endIndex] = true;
53                 _edges[endIndex][startIndex] = true;
54 
55                 return this;
56             }
57 
58             public Graph<T> AddDirectedEdge(T startItem, T endItem)
59             {
60                 var startIndex = this.GetVertexIndex(startItem);
61                 var endIndex = this.GetVertexIndex(endItem);
62 
63                 _edges[startIndex][endIndex] = true;
64 
65                 return this;
66             }
67 
68             #endregion
69         }

圖的常見算法

遍歷

本文的遍歷算法只適合一般的無向圖。

深度優先遍歷

深度優先遍歷的原則是:盡可能的離開始節點遠,二叉樹的三種遍歷算法都屬於這種算法。

示例

從 A 開始的遍歷順序為(鄰接的遍歷順序為字母升序):ABCEDBF。

棧版本

 1             #region 深度優先遍歷:棧版本
 2 
 3             public void DepthFirstSearch(T startItem, Action<T> action)
 4             {
 5                 var startIndex = this.GetVertexIndex(startItem);
 6                 var stack = new Stack<int>();
 7 
 8                 this.DepthFirstSearchVisit(stack, startIndex, action);
 9                 while (stack.Count > 0)
10                 {
11                     var adjoinVertexIndex = this.GetNextUnVisitedAdjoinVertexIndex(stack.Peek());
12                     if (adjoinVertexIndex >= 0)
13                     {
14                         this.DepthFirstSearchVisit(stack, adjoinVertexIndex, action);
15                     }
16                     else
17                     {
18                         stack.Pop();
19                     }
20                 }
21 
22                 this.ResetVisited();
23             }
24 
25             private void DepthFirstSearchVisit(Stack<int> stack, int index, Action<T> action)
26             {
27                 _vertexs[index].WasVisited = true;
28                 action(_vertexs[index].Value);
29                 stack.Push(index);
30             }
31 
32             #endregion

遞歸版本

 1             #region 深度優先遍歷:遞歸版本
 2 
 3             public void DepthFirstSearchRecursion(T startItem, Action<T> action)
 4             {
 5                 var startIndex = this.GetVertexIndex(startItem);
 6 
 7                 this.DepthFirstSearchRecursionVisit(startIndex, action);
 8 
 9                 this.ResetVisited();
10             }
11 
12             private void DepthFirstSearchRecursionVisit(int index, Action<T> action)
13             {
14                 _vertexs[index].WasVisited = true;
15                 action(_vertexs[index].Value);
16 
17                 var adjoinVertexIndex = this.GetNextUnVisitedAdjoinVertexIndex(index);
18                 while (adjoinVertexIndex >= 0)
19                 {
20                     this.DepthFirstSearchRecursionVisit(adjoinVertexIndex, action);
21                     adjoinVertexIndex = this.GetNextUnVisitedAdjoinVertexIndex(index);
22                 }
23             }
24 
25             #endregion

廣度優先遍歷

廣度優先遍歷的原則是:盡可能的離開始節點近。這個算法沒有辦法通過遞歸實現,多數都是使用的隊列(終於發現了隊列的又一個用處)。

示例

從 A 開始的遍歷順序為(鄰接的遍歷順序為字母升序):ABCEDFB。

代碼

 1             #region 廣度優先遍歷
 2 
 3             public void BreadthFirstSearch(T startItem, Action<T> action)
 4             {
 5                 var startIndex = this.GetVertexIndex(startItem);
 6                 var queue = new Queue<int>();
 7 
 8                 this.BreadthFirstSearchVisit(queue, startIndex, action);
 9                 while (queue.Count > 0)
10                 {
11                     var adjoinVertexIndexs = this.GetNextUnVisitedAdjoinVertexIndexs(queue.Dequeue());
12                     foreach (var adjoinVertexIndex in adjoinVertexIndexs)
13                     {
14                         this.BreadthFirstSearchVisit(queue, adjoinVertexIndex, action);
15                     }
16                 }
17 
18                 this.ResetVisited();
19             }
20 
21             private void BreadthFirstSearchVisit(Queue<int> queue, int index, Action<T> action)
22             {
23                 _vertexs[index].WasVisited = true;
24                 action(_vertexs[index].Value);
25                 queue.Enqueue(index);
26             }
27 
28             #endregion

最小生成樹

本文的最小生成樹算法只適合一般的無向圖。

將 N 個頂點連接起來的最小樹叫:最小生成樹。

給定一個顆有 N 個頂點的連通圖,則最小生成樹的邊為:N - 1。

不同的遍歷算法生成的最小生成樹是不同的,下面使用廣度優先遍歷來生成最小樹。

示例

從 A 開始的遍歷順序為(鄰接的遍歷順序為字母升序):A->B、B->C、C->E、E->D、E->F、D->B。

代碼

 1             #region 最小生成樹
 2 
 3             public void DisplayMinimumSpanningTree(T startItem)
 4             {
 5                 var startIndex = this.GetVertexIndex(startItem);
 6                 var queue = new Queue<int>();
 7 
 8                 _vertexs[startIndex].WasVisited = true;
 9                 queue.Enqueue(startIndex);
10                 while (queue.Count > 0)
11                 {
12                     var currentIndex = queue.Dequeue();
13                     var adjoinVertexIndexs = this.GetNextUnVisitedAdjoinVertexIndexs(currentIndex);
14                     foreach (var adjoinVertexIndex in adjoinVertexIndexs)
15                     {
16                         Console.WriteLine(_vertexs[currentIndex].Value + "->" + _vertexs[adjoinVertexIndex].Value);
17 
18                         _vertexs[adjoinVertexIndex].WasVisited = true;
19                         queue.Enqueue(adjoinVertexIndex);
20                     }
21                 }
22 
23                 this.ResetVisited();
24             }
25 
26             #endregion

拓撲排序

拓撲排序只適合定向圖,且圖中不存在循環結構。其算法非常簡單,依次獲取圖中的沒有鄰接頂點的頂點,然后刪除該頂點。

示例

上圖出現了循環結構,假如沒有頂點 C,拓撲排序的結果為(頂點的遍歷順序為字母升序):EFDAB。

算法

 1             #region 拓撲排序
 2 
 3             public List<T> TopologySort()
 4             {
 5                 var cloneVertexs = (Vertex<T>[])_vertexs.Clone();
 6                 var cloneEdges = (bool[][])_edges.Clone();
 7                 var cloneVertexCount = _vertexCount;
 8 
 9                 var results = new List<T>();
10                 while (_vertexCount > 0)
11                 {
12                     var successor = this.NextSuccessor();
13                     if (successor == -1)
14                     {
15                         throw new InvalidOperationException("出現循環了!");
16                     }
17 
18                     results.Insert(0, _vertexs[successor].Value);
19 
20                     this.RemoveVertex(successor);
21                     _vertexCount--;
22                 }
23 
24                 _vertexs = cloneVertexs;
25                 _edges = cloneEdges;
26                 _vertexCount = cloneVertexCount;
27 
28                 return results;
29             }
30 
31             private int NextSuccessor()
32             {
33                 for (var row = 0; row < _vertexCount; row++)
34                 {
35                     if (_edges[row].Take(_vertexCount).All(x => !x))
36                     {
37                         return row;
38                     }
39                 }
40 
41                 return -1;
42             }
43 
44             private void RemoveVertex(int successor)
45             {
46                 for (int i = successor; i < _vertexCount - 1; i++)
47                 {
48                     _vertexs[i] = _vertexs[i + 1];
49                 }
50 
51                 for (int row = successor; row < _vertexCount - 1; row++)
52                 {
53                     for (int column = 0; column < _vertexCount; column++)
54                     {
55                         _edges[row][column] = _edges[row + 1][column];
56                     }
57                 }
58 
59                 for (int column = successor; column < _vertexCount - 1; column++)
60                 {
61                     for (int row = 0; row < _vertexCount; row++)
62                     {
63                         _edges[row][column] = _edges[row][column + 1];
64                     }
65                 }
66             }
67 
68             #endregion

完整代碼

  1 using System;
  2 using System.Collections.Generic;
  3 using System.Collections.ObjectModel;
  4 using System.Linq;
  5 using System.Text;
  6 using System.Threading.Tasks;
  7 
  8 namespace DataStuctureStudy.Graphs
  9 {
 10     class GraphTest
 11     {
 12         public static void Test()
 13         {
 14             var graph = new Graph<String>(50);
 15             graph
 16                 .AddVertex("A")
 17                 .AddVertex("B")
 18                 .AddVertex("C")
 19                 .AddVertex("D")
 20                 .AddVertex("E")
 21                 .AddVertex("F")
 22                 .AddVertex("G")
 23                 .AddVertex("H")
 24                 .AddVertex("I");
 25             graph
 26                 .AddDirectedEdge("A", "B").AddDirectedEdge("A", "C").AddDirectedEdge("A", "D").AddDirectedEdge("A", "E")
 27                 .AddDirectedEdge("B", "F")
 28                 .AddDirectedEdge("D", "G")
 29                 .AddDirectedEdge("F", "H")
 30                 .AddDirectedEdge("G", "I");
 31 
 32             Console.WriteLine("深度遍歷,棧版本:");
 33             graph.DepthFirstSearch("A", Console.Write);
 34             Console.WriteLine();
 35             Console.WriteLine();
 36 
 37             Console.WriteLine("深度遍歷,遞歸版本:");
 38             graph.DepthFirstSearchRecursion("A", Console.Write);
 39             Console.WriteLine();
 40             Console.WriteLine();
 41 
 42             Console.WriteLine("廣度遍歷:");
 43             graph.BreadthFirstSearch("A", Console.Write);
 44             Console.WriteLine();
 45             Console.WriteLine();
 46 
 47             Console.WriteLine("最小生成樹:");
 48             graph.DisplayMinimumSpanningTree("A");
 49             Console.WriteLine();
 50             Console.WriteLine();
 51 
 52             Console.WriteLine("拓撲排序:");
 53             var results = graph.TopologySort();
 54             Console.WriteLine(String.Join("->", results.ToArray()));
 55             Console.WriteLine();
 56         }
 57 
 58         class Vertex<T>
 59         {
 60             public T Value { get; set; }
 61 
 62             public bool WasVisited { get; set; }
 63         }
 64 
 65         class Graph<T>
 66         {
 67             #region 私有字段
 68 
 69             private int _maxSize;
 70             private Vertex<T>[] _vertexs;
 71             private bool[][] _edges;
 72             private int _vertexCount = 0;
 73 
 74             #endregion
 75 
 76             #region 構造方法
 77 
 78             public Graph(int maxSize)
 79             {
 80                 _maxSize = maxSize;
 81                 _vertexs = new Vertex<T>[_maxSize];
 82                 _edges = new bool[_maxSize][];
 83                 for (var i = 0; i < _maxSize; i++)
 84                 {
 85                     _edges[i] = new bool[_maxSize];
 86                 }
 87             }
 88 
 89             #endregion
 90 
 91             #region 添加頂點
 92 
 93             public Graph<T> AddVertex(T value)
 94             {
 95                 _vertexs[_vertexCount++] = new Vertex<T> { Value = value };
 96 
 97                 return this;
 98             }
 99 
100             #endregion
101 
102             #region 添加邊
103 
104             public Graph<T> AddUnDirectedEdge(T startItem, T endItem)
105             {
106                 var startIndex = this.GetVertexIndex(startItem);
107                 var endIndex = this.GetVertexIndex(endItem);
108 
109                 _edges[startIndex][endIndex] = true;
110                 _edges[endIndex][startIndex] = true;
111 
112                 return this;
113             }
114 
115             public Graph<T> AddDirectedEdge(T startItem, T endItem)
116             {
117                 var startIndex = this.GetVertexIndex(startItem);
118                 var endIndex = this.GetVertexIndex(endItem);
119 
120                 _edges[startIndex][endIndex] = true;
121 
122                 return this;
123             }
124 
125             #endregion
126 
127             #region 深度優先遍歷:棧版本
128 
129             public void DepthFirstSearch(T startItem, Action<T> action)
130             {
131                 var startIndex = this.GetVertexIndex(startItem);
132                 var stack = new Stack<int>();
133 
134                 this.DepthFirstSearchVisit(stack, startIndex, action);
135                 while (stack.Count > 0)
136                 {
137                     var adjoinVertexIndex = this.GetNextUnVisitedAdjoinVertexIndex(stack.Peek());
138                     if (adjoinVertexIndex >= 0)
139                     {
140                         this.DepthFirstSearchVisit(stack, adjoinVertexIndex, action);
141                     }
142                     else
143                     {
144                         stack.Pop();
145                     }
146                 }
147 
148                 this.ResetVisited();
149             }
150 
151             private void DepthFirstSearchVisit(Stack<int> stack, int index, Action<T> action)
152             {
153                 _vertexs[index].WasVisited = true;
154                 action(_vertexs[index].Value);
155                 stack.Push(index);
156             }
157 
158             #endregion
159 
160             #region 深度優先遍歷:遞歸版本
161 
162             public void DepthFirstSearchRecursion(T startItem, Action<T> action)
163             {
164                 var startIndex = this.GetVertexIndex(startItem);
165 
166                 this.DepthFirstSearchRecursionVisit(startIndex, action);
167 
168                 this.ResetVisited();
169             }
170 
171             private void DepthFirstSearchRecursionVisit(int index, Action<T> action)
172             {
173                 _vertexs[index].WasVisited = true;
174                 action(_vertexs[index].Value);
175 
176                 var adjoinVertexIndex = this.GetNextUnVisitedAdjoinVertexIndex(index);
177                 while (adjoinVertexIndex >= 0)
178                 {
179                     this.DepthFirstSearchRecursionVisit(adjoinVertexIndex, action);
180                     adjoinVertexIndex = this.GetNextUnVisitedAdjoinVertexIndex(index);
181                 }
182             }
183 
184             #endregion
185 
186             #region 廣度優先遍歷
187 
188             public void BreadthFirstSearch(T startItem, Action<T> action)
189             {
190                 var startIndex = this.GetVertexIndex(startItem);
191                 var queue = new Queue<int>();
192 
193                 this.BreadthFirstSearchVisit(queue, startIndex, action);
194                 while (queue.Count > 0)
195                 {
196                     var adjoinVertexIndexs = this.GetNextUnVisitedAdjoinVertexIndexs(queue.Dequeue());
197                     foreach (var adjoinVertexIndex in adjoinVertexIndexs)
198                     {
199                         this.BreadthFirstSearchVisit(queue, adjoinVertexIndex, action);
200                     }
201                 }
202 
203                 this.ResetVisited();
204             }
205 
206             private void BreadthFirstSearchVisit(Queue<int> queue, int index, Action<T> action)
207             {
208                 _vertexs[index].WasVisited = true;
209                 action(_vertexs[index].Value);
210                 queue.Enqueue(index);
211             }
212 
213             #endregion
214 
215             #region 最小生成樹
216 
217             public void DisplayMinimumSpanningTree(T startItem)
218             {
219                 var startIndex = this.GetVertexIndex(startItem);
220                 var queue = new Queue<int>();
221 
222                 _vertexs[startIndex].WasVisited = true;
223                 queue.Enqueue(startIndex);
224                 while (queue.Count > 0)
225                 {
226                     var currentIndex = queue.Dequeue();
227                     var adjoinVertexIndexs = this.GetNextUnVisitedAdjoinVertexIndexs(currentIndex);
228                     foreach (var adjoinVertexIndex in adjoinVertexIndexs)
229                     {
230                         Console.WriteLine(_vertexs[currentIndex].Value + "->" + _vertexs[adjoinVertexIndex].Value);
231 
232                         _vertexs[adjoinVertexIndex].WasVisited = true;
233                         queue.Enqueue(adjoinVertexIndex);
234                     }
235                 }
236 
237                 this.ResetVisited();
238             }
239 
240             #endregion
241 
242             #region 拓撲排序
243 
244             public List<T> TopologySort()
245             {
246                 var cloneVertexs = (Vertex<T>[])_vertexs.Clone();
247                 var cloneEdges = (bool[][])_edges.Clone();
248                 var cloneVertexCount = _vertexCount;
249 
250                 var results = new List<T>();
251                 while (_vertexCount > 0)
252                 {
253                     var successor = this.NextSuccessor();
254                     if (successor == -1)
255                     {
256                         throw new InvalidOperationException("出現循環了!");
257                     }
258 
259                     results.Insert(0, _vertexs[successor].Value);
260 
261                     this.RemoveVertex(successor);
262                     _vertexCount--;
263                 }
264 
265                 _vertexs = cloneVertexs;
266                 _edges = cloneEdges;
267                 _vertexCount = cloneVertexCount;
268 
269                 return results;
270             }
271 
272             private int NextSuccessor()
273             {
274                 for (var row = 0; row < _vertexCount; row++)
275                 {
276                     if (_edges[row].Take(_vertexCount).All(x => !x))
277                     {
278                         return row;
279                     }
280                 }
281 
282                 return -1;
283             }
284 
285             private void RemoveVertex(int successor)
286             {
287                 for (int i = successor; i < _vertexCount - 1; i++)
288                 {
289                     _vertexs[i] = _vertexs[i + 1];
290                 }
291 
292                 for (int row = successor; row < _vertexCount - 1; row++)
293                 {
294                     for (int column = 0; column < _vertexCount; column++)
295                     {
296                         _edges[row][column] = _edges[row + 1][column];
297                     }
298                 }
299 
300                 for (int column = successor; column < _vertexCount - 1; column++)
301                 {
302                     for (int row = 0; row < _vertexCount; row++)
303                     {
304                         _edges[row][column] = _edges[row][column + 1];
305                     }
306                 }
307             }
308 
309             #endregion
310 
311             #region 幫助方法
312 
313             private int GetVertexIndex(T item)
314             {
315                 for (var i = 0; i < _vertexCount; i++)
316                 {
317                     if (_vertexs[i].Value.Equals(item))
318                     {
319                         return i;
320                     }
321                 }
322                 return -1;
323             }
324 
325             private int GetNextUnVisitedAdjoinVertexIndex(int index)
326             {
327                 for (var i = 0; i < _vertexCount; i++)
328                 {
329                     if (_edges[index][i] && _vertexs[i].WasVisited == false)
330                     {
331                         return i;
332                     }
333                 }
334 
335                 return -1;
336             }
337 
338             private List<int> GetNextUnVisitedAdjoinVertexIndexs(int index)
339             {
340                 var results = new List<int>();
341 
342                 for (var i = 0; i < _vertexCount; i++)
343                 {
344                     if (_edges[index][i] && _vertexs[i].WasVisited == false)
345                     {
346                         results.Add(i);
347                     }
348                 }
349 
350                 return results;
351             }
352 
353             private void ResetVisited()
354             {
355                 for (var i = 0; i < _vertexCount; i++)
356                 {
357                     _vertexs[i].WasVisited = false;
358                 }
359             }
360 
361             #endregion
362         }
363     }
364 }
View Code

備注

本文沒有解釋權重圖,下篇再介紹。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM