对于一个给定的连通的无向图 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; } |