算法:图(Graph)的遍历、最小生成树和拓扑排序

时间:2022-04-14 11:37:59

背景

不同的数据结构有不同的用途,像:数组、链表、队列、栈多数是用来做为基本的工具使用,二叉树多用来作为已排序元素列表的存储,B 树用在存储中,本文介绍的 Graph 多数是为了解决现实问题(说到底,所有的数据结构都是这个目的),如:网络布局、任务安排等。

图的基本概念

示例

算法:图(Graph)的遍历、最小生成树和拓扑排序

顶点(Vertex)

上图的 1、2、3、4、5、6 就是顶点。

邻接(Adjoin)

如果 A 和 B 通过定向边相连,且方向为 A -> B,则 B 为 A 的邻接,如果相连的边是没有方向的,则 A 和 B 互为邻接。

边(Edge)

顶点之间的连线就是边。

连通图(Connected Graph)

不考虑边的方向性,从任何一个节点都可以遍历到其它节点。本文的所有算法(除了拓扑排序)只适合连通图。

定向图(Directed Graph)

图中的所有边都带方向。

算法:图(Graph)的遍历、最小生成树和拓扑排序

权重图(Weighted Graph)

图中的所有边都有权重。

算法:图(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 }

图的常见算法

遍历

本文的遍历算法只适合一般的无向图。

深度优先遍历

深度优先遍历的原则是:尽可能的离开始节点远,二叉树的三种遍历算法都属于这种算法。

示例

算法:图(Graph)的遍历、最小生成树和拓扑排序

从 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

广度优先遍历

广度优先遍历的原则是:尽可能的离开始节点近。这个算法没有办法通过递归实现,多数都是使用的队列(终于发现了队列的又一个用处)。

示例

算法:图(Graph)的遍历、最小生成树和拓扑排序

从 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。

不同的遍历算法生成的最小生成树是不同的,下面使用广度优先遍历来生成最小树。

示例

算法:图(Graph)的遍历、最小生成树和拓扑排序

从 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

拓扑排序

拓扑排序只适合定向图,且图中不存在循环结构。其算法非常简单,依次获取图中的没有邻接顶点的顶点,然后删除该顶点。

示例

算法:图(Graph)的遍历、最小生成树和拓扑排序

上图出现了循环结构,假如没有顶点 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

完整代码

算法:图(Graph)的遍历、最小生成树和拓扑排序算法:图(Graph)的遍历、最小生成树和拓扑排序
  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

备注

本文没有解释权重图,下篇再介绍。