首页 > 代码库 > Kruskal 最小生成树算法

Kruskal 最小生成树算法

对于一个给定的连通的无向图 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)的思想进行设计,其选择的贪心策略就是,每次都选择权重最小的但未形成环路的边加入到生成树中。其算法结构如下:

  1. 将所有的边按照权重非递减排序;
  2. 选择最小权重的边,判断是否其在当前的生成树中形成了一个环路。如果环路没有形成,则将该边加入树中,否则放弃。
  3. 重复步骤 2,直到有 V - 1 条边在生成树中。

上述步骤 2 中使用了 Union-Find 算法来判断是否存在环路。

例如,下面是一个无向连通图 G。

技术分享

图 G 中包含 9 个顶点和 14 条边,所以期待的最小生成树应包含 (9 - 1) = 8 条边。

首先对所有的边按照权重的非递减顺序排序:

Weight Src Dest1 7 62 8 22 6 54 0 14 2 56 8 67 2 37 7 88 0 78 1 29 3 410 5 411 1 714 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 using System;  2 using System.Collections.Generic;  3 using System.Linq;  4   5 namespace GraphAlgorithmTesting  6 {  7   class Program  8   {  9     static void Main(string[] args) 10     { 11       Graph g = new Graph(9); 12       g.AddEdge(0, 1, 4); 13       g.AddEdge(0, 7, 8); 14       g.AddEdge(1, 2, 8); 15       g.AddEdge(1, 7, 11); 16       g.AddEdge(2, 3, 7); 17       g.AddEdge(2, 5, 4); 18       g.AddEdge(8, 2, 2); 19       g.AddEdge(3, 4, 9); 20       g.AddEdge(3, 5, 14); 21       g.AddEdge(5, 4, 10); 22       g.AddEdge(6, 5, 2); 23       g.AddEdge(8, 6, 6); 24       g.AddEdge(7, 6, 1); 25       g.AddEdge(7, 8, 7); 26  27       Console.WriteLine(); 28       Console.WriteLine("Graph Vertex Count : {0}", g.VertexCount); 29       Console.WriteLine("Graph Edge Count : {0}", g.EdgeCount); 30       Console.WriteLine(); 31  32       Console.WriteLine("Is there cycle in graph: {0}", g.HasCycle()); 33       Console.WriteLine(); 34  35       Edge[] mst = g.Kruskal(); 36       Console.WriteLine("MST Edges:"); 37       foreach (var edge in mst) 38       { 39         Console.WriteLine("\t{0}", edge); 40       } 41  42       Console.ReadKey(); 43     } 44  45     class Edge 46     { 47       public Edge(int begin, int end, int weight) 48       { 49         this.Begin = begin; 50         this.End = end; 51         this.Weight = weight; 52       } 53  54       public int Begin { get; private set; } 55       public int End { get; private set; } 56       public int Weight { get; private set; } 57  58       public override string ToString() 59       { 60         return string.Format( 61           "Begin[{0}], End[{1}], Weight[{2}]", 62           Begin, End, Weight); 63       } 64     } 65  66     class Subset 67     { 68       public int Parent { get; set; } 69       public int Rank { get; set; } 70     } 71  72     class Graph 73     { 74       private Dictionary<int, List<Edge>> _adjacentEdges 75         = new Dictionary<int, List<Edge>>(); 76  77       public Graph(int vertexCount) 78       { 79         this.VertexCount = vertexCount; 80       } 81  82       public int VertexCount { get; private set; } 83  84       public IEnumerable<int> Vertices { get { return _adjacentEdges.Keys; } } 85  86       public IEnumerable<Edge> Edges 87       { 88         get { return _adjacentEdges.Values.SelectMany(e => e); } 89       } 90  91       public int EdgeCount { get { return this.Edges.Count(); } } 92  93       public void AddEdge(int begin, int end, int weight) 94       { 95         if (!_adjacentEdges.ContainsKey(begin)) 96         { 97           var edges = new List<Edge>(); 98           _adjacentEdges.Add(begin, edges); 99         }100 101         _adjacentEdges[begin].Add(new Edge(begin, end, weight));102       }103 104       private int Find(Subset[] subsets, int i)105       {106         // find root and make root as parent of i (path compression)107         if (subsets[i].Parent != i)108           subsets[i].Parent = Find(subsets, subsets[i].Parent);109 110         return subsets[i].Parent;111       }112 113       private void Union(Subset[] subsets, int x, int y)114       {115         int xroot = Find(subsets, x);116         int yroot = Find(subsets, y);117 118         // Attach smaller rank tree under root of high rank tree119         // (Union by Rank)120         if (subsets[xroot].Rank < subsets[yroot].Rank)121           subsets[xroot].Parent = yroot;122         else if (subsets[xroot].Rank > subsets[yroot].Rank)123           subsets[yroot].Parent = xroot;124 125         // If ranks are same, then make one as root and increment126         // its rank by one127         else128         {129           subsets[yroot].Parent = xroot;130           subsets[xroot].Rank++;131         }132       }133 134       public bool HasCycle()135       {136         Subset[] subsets = new Subset[VertexCount];137         for (int i = 0; i < subsets.Length; i++)138         {139           subsets[i] = new Subset();140           subsets[i].Parent = i;141           subsets[i].Rank = 0;142         }143 144         // Iterate through all edges of graph, find subset of both145         // vertices of every edge, if both subsets are same, 146         // then there is cycle in graph.147         foreach (var edge in this.Edges)148         {149           int x = Find(subsets, edge.Begin);150           int y = Find(subsets, edge.End);151 152           if (x == y)153           {154             return true;155           }156 157           Union(subsets, x, y);158         }159 160         return false;161       }162 163       public Edge[] Kruskal()164       {165         // This will store the resultant MST166         Edge[] mst = new Edge[VertexCount - 1];167 168         // Step 1: Sort all the edges in non-decreasing order of their weight169         // If we are not allowed to change the given graph, we can create a copy of170         // array of edges171         var sortedEdges = this.Edges.OrderBy(t => t.Weight);172         var enumerator = sortedEdges.GetEnumerator();173 174         // Allocate memory for creating V ssubsets175         // Create V subsets with single elements176         Subset[] subsets = new Subset[VertexCount];177         for (int i = 0; i < subsets.Length; i++)178         {179           subsets[i] = new Subset();180           subsets[i].Parent = i;181           subsets[i].Rank = 0;182         }183 184         // Number of edges to be taken is equal to V-1185         int e = 0;186         while (e < VertexCount - 1)187         {188           // Step 2: Pick the smallest edge. And increment the index189           // for next iteration190           Edge nextEdge;191           if (enumerator.MoveNext())192           {193             nextEdge = enumerator.Current;194 195             int x = Find(subsets, nextEdge.Begin);196             int y = Find(subsets, nextEdge.End);197 198             // If including this edge does‘t cause cycle, include it199             // in result and increment the index of result for next edge200             if (x != y)201             {202               mst[e++] = nextEdge;203               Union(subsets, x, y);204             }205             else206             {207               // Else discard the nextEdge208             }209           }210         }211 212         return mst;213       }214     }215   }216 }

输出结果如下:

技术分享

参考资料

  • Connectivity in a directed graph
  • Strongly Connected Components
  • Tarjan‘s Algorithm to find Strongly Connected Components

本篇文章《Kruskal 最小生成树算法》由 Dennis Gao 发表自博客园,未经作者本人同意禁止任何形式的转载,任何自动或人为的爬虫转载行为均为耍流氓。

Kruskal 最小生成树算法