算法:图(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備注
本文沒有解釋權重圖,下篇再介紹。
?
轉載于:https://www.cnblogs.com/happyframework/p/3493606.html
總結
以上是生活随笔為你收集整理的算法:图(Graph)的遍历、最小生成树和拓扑排序的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 神经网络预测
- 下一篇: ORACLE创建用户,建表空间,授予权限