對於一個給定的連通的無向圖 G = (V, E),希望找到一個無迴路的子集 T,T 是 E 的子集,它連線了所有的頂點,且其權值之和為最小。
因為 T 無迴路且連線所有的頂點,所以它必然是一棵樹,稱為生成樹(Spanning Tree),因為它生成了圖 G。顯然,由於樹 T 連線了所有的頂點,所以樹 T 有 V – 1 條邊。一張圖 G 可以有很多棵生成樹,而把確定權值最小的樹 T 的問題稱為最小生成樹問題(Minimum Spanning Tree)。術語 “最小生成樹” 實際上是 “最小權值生成樹” 的縮寫。
Kruskal 演算法提供一種在 O(ElogV) 執行時間確定最小生成樹的方案。Kruskal 演算法基於貪心演算法(Greedy Algorithm)的思想進行設計,其選擇的貪心策略就是,每次都選擇權重最小的但未形成環路的邊加入到生成樹中。其演算法結構如下:
- 將所有的邊按照權重非遞減排序;
- 選擇最小權重的邊,判斷是否其在當前的生成樹中形成了一個環路。如果環路沒有形成,則將該邊加入樹中,否則放棄。
- 重複步驟 2,直到有 V – 1 條邊在生成樹中。
上述步驟 2 中使用了 Union-Find 演算法來判斷是否存在環路。
例如,下面是一個無向連通圖 G。
圖 G 中包含 9 個頂點和 14 條邊,所以期待的最小生成樹應包含 (9 – 1) = 8 條邊。
首先對所有的邊按照權重的非遞減順序排序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
Weight Src Dest 1 7 6 2 8 2 2 6 5 4 0 1 4 2 5 6 8 6 7 2 3 7 7 8 8 0 7 8 1 2 9 3 4 10 5 4 11 1 7 14 3 5 |
然後從排序後的列表中選擇權重最小的邊。
1. 選擇邊 {7, 6},無環路形成,包含在生成樹中。
2. 選擇邊 {8, 2},無環路形成,包含在生成樹中。
3. 選擇邊 {6, 5},無環路形成,包含在生成樹中。
4. 選擇邊 {0, 1},無環路形成,包含在生成樹中。
5. 選擇邊 {2, 5},無環路形成,包含在生成樹中。
6. 選擇邊 {8, 6},有環路形成,放棄。
7. 選擇邊 {2, 3},無環路形成,包含在生成樹中。
8. 選擇邊 {7, 8},有環路形成,放棄。
9. 選擇邊 {0, 7},無環路形成,包含在生成樹中。
10. 選擇邊 {1, 2},有環路形成,放棄。
11. 選擇邊 {3, 4},無環路形成,包含在生成樹中。
12. 由於當前生成樹中已經包含 V – 1 條邊,演算法結束。
C# 實現的 Kruskal 演算法如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 |
using System; using System.Collections.Generic; using System.Linq; namespace GraphAlgorithmTesting { class Program { static void Main(string[] args) { Graph g = new Graph(9); g.AddEdge(0, 1, 4); g.AddEdge(0, 7, 8); g.AddEdge(1, 2, 8); g.AddEdge(1, 7, 11); g.AddEdge(2, 3, 7); g.AddEdge(2, 5, 4); g.AddEdge(8, 2, 2); g.AddEdge(3, 4, 9); g.AddEdge(3, 5, 14); g.AddEdge(5, 4, 10); g.AddEdge(6, 5, 2); g.AddEdge(8, 6, 6); g.AddEdge(7, 6, 1); g.AddEdge(7, 8, 7); Console.WriteLine(); Console.WriteLine("Graph Vertex Count : {0}", g.VertexCount); Console.WriteLine("Graph Edge Count : {0}", g.EdgeCount); Console.WriteLine(); Console.WriteLine("Is there cycle in graph: {0}", g.HasCycle()); Console.WriteLine(); Edge[] mst = g.Kruskal(); Console.WriteLine("MST Edges:"); foreach (var edge in mst) { Console.WriteLine("\t{0}", edge); } Console.ReadKey(); } class Edge { public Edge(int begin, int end, int weight) { this.Begin = begin; this.End = end; this.Weight = weight; } public int Begin { get; private set; } public int End { get; private set; } public int Weight { get; private set; } public override string ToString() { return string.Format( "Begin[{0}], End[{1}], Weight[{2}]", Begin, End, Weight); } } class Subset { public int Parent { get; set; } public int Rank { get; set; } } class Graph { private Dictionary<int, List<Edge>> _adjacentEdges = new Dictionary<int, List<Edge>>(); public Graph(int vertexCount) { this.VertexCount = vertexCount; } public int VertexCount { get; private set; } public IEnumerable<int> Vertices { get { return _adjacentEdges.Keys; } } public IEnumerable<Edge> Edges { get { return _adjacentEdges.Values.SelectMany(e => e); } } public int EdgeCount { get { return this.Edges.Count(); } } public void AddEdge(int begin, int end, int weight) { if (!_adjacentEdges.ContainsKey(begin)) { var edges = new List<Edge>(); _adjacentEdges.Add(begin, edges); } _adjacentEdges[begin].Add(new Edge(begin, end, weight)); } private int Find(Subset[] subsets, int i) { // find root and make root as parent of i (path compression) if (subsets[i].Parent != i) subsets[i].Parent = Find(subsets, subsets[i].Parent); return subsets[i].Parent; } private void Union(Subset[] subsets, int x, int y) { int xroot = Find(subsets, x); int yroot = Find(subsets, y); // Attach smaller rank tree under root of high rank tree // (Union by Rank) if (subsets[xroot].Rank < subsets[yroot].Rank) subsets[xroot].Parent = yroot; else if (subsets[xroot].Rank > subsets[yroot].Rank) subsets[yroot].Parent = xroot; // If ranks are same, then make one as root and increment // its rank by one else { subsets[yroot].Parent = xroot; subsets[xroot].Rank++; } } public bool HasCycle() { Subset[] subsets = new Subset[VertexCount]; for (int i = 0; i < subsets.Length; i++) { subsets[i] = new Subset(); subsets[i].Parent = i; subsets[i].Rank = 0; } // Iterate through all edges of graph, find subset of both // vertices of every edge, if both subsets are same, // then there is cycle in graph. foreach (var edge in this.Edges) { int x = Find(subsets, edge.Begin); int y = Find(subsets, edge.End); if (x == y) { return true; } Union(subsets, x, y); } return false; } public Edge[] Kruskal() { // This will store the resultant MST Edge[] mst = new Edge[VertexCount - 1]; // Step 1: Sort all the edges in non-decreasing order of their weight // If we are not allowed to change the given graph, we can create a copy of // array of edges var sortedEdges = this.Edges.OrderBy(t => t.Weight); var enumerator = sortedEdges.GetEnumerator(); // Allocate memory for creating V ssubsets // Create V subsets with single elements Subset[] subsets = new Subset[VertexCount]; for (int i = 0; i < subsets.Length; i++) { subsets[i] = new Subset(); subsets[i].Parent = i; subsets[i].Rank = 0; } // Number of edges to be taken is equal to V-1 int e = 0; while (e < VertexCount - 1) { // Step 2: Pick the smallest edge. And increment the index // for next iteration Edge nextEdge; if (enumerator.MoveNext()) { nextEdge = enumerator.Current; int x = Find(subsets, nextEdge.Begin); int y = Find(subsets, nextEdge.End); // If including this edge does't cause cycle, include it // in result and increment the index of result for next edge if (x != y) { mst[e++] = nextEdge; Union(subsets, x, y); } else { // Else discard the nextEdge } } } return mst; } } } } |
輸出結果如下: