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

背景

不同的数据结构有不同的用途,像:数组、链表、队列、栈多数是用来做为基本的工具使用,二叉树多用来作为已排序元素列表的存储,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)的遍历、最小生成树和拓扑排序
  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

备注

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

 

你可能感兴趣的:(最小生成树)