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 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 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 tree
119         // (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 increment
126         // its rank by one
127         else
128         {
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 both
145         // 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 MST
166         Edge[] mst = new Edge[VertexCount - 1];
167
168         // Step 1: Sort all the edges in non-decreasing order of their weight
169         // If we are not allowed to change the given graph, we can create a copy of
170         // array of edges
171         var sortedEdges = this.Edges.OrderBy(t => t.Weight);
172         var enumerator = sortedEdges.GetEnumerator();
173
174         // Allocate memory for creating V ssubsets
175         // Create V subsets with single elements
176         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-1
185         int e = 0;
186         while (e < VertexCount - 1)
187         {
188           // Step 2: Pick the smallest edge. And increment the index
189           // for next iteration
190           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 it
199             // in result and increment the index of result for next edge
200             if (x != y)
201             {
202               mst[e++] = nextEdge;
203               Union(subsets, x, y);
204             }
205             else
206             {
207               // Else discard the nextEdge
208             }
209           }
210         }
211
212         return mst;
213       }
214     }
215   }
216 }

输出结果如下:

参考资料

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

时间: 2024-12-10 13:13:08

Kruskal 最小生成树算法的相关文章

[算法系列之二十七]Kruskal最小生成树算法

简介 求最小生成树一共有两种算法,一个是就是本文所说的Kruskal算法,另一个就是Prime算法.在详细讲解Kruskal最小生成树算法之前,让我们先回顾一下什么是最小生成树. 我们有一个带权值的图,我们要求找到一个所有生成树中具有最小权值的生成树.如下图所示,T是图G的生成树.但不是具有最小权值的生成树. 我们可以把他们想象成一组岛屿和连接它们的可能的桥梁.当然修桥是非常昂贵和费时的,所以我们必须要知道建设什么样的桥梁去连接各个岛.不过有一个重要的问题,建设这样一组连接所有岛屿的桥梁的最低价

Kruskal最小生成树算法

将所有的边按照权重非递减排序: 选择最小权重的边,判断是否其在当前的生成树中形成了一个环路.如果环路没有形成,则将该边加入树中,否则放弃. 重复步骤 2,直到有 V – 1 条边在生成树中. http://blog.jobbole.com/83939/ using System; using System.Collections.Generic; using System.Linq; namespace GraphAlgorithmTesting { class Program { static

USACO 2007 December Contest, Silver Problem 2. Building Roads Kruskal最小生成树算法

PROBLEM: (ENGLISH VERSION) Farmer John had just acquired several new farms! He wants to connect the farms with roads so that he can travel from any farm to any other farm via a sequence of roads; roads already connect some of the farms. Each of the N

贪心算法(2)-Kruskal最小生成树

什么是最小生成树? 生成树是相对图来说的,一个图的生成树是一个树并把图的所有顶点连接在一起.一个图可以有许多不同的生成树.一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边.最小生成树其实是最小权重生成树的简称.生成树的权重是考虑到了生成树的每条边的权重的总和. 最小生成树有几条边? 最小生成树有(V – 1)条边,其中V是给定的图的顶点数量. Kruskal算法 下面是步骤寻找MST使用Kruskal算法 1 1,按照所有边的权重

最小生成树之克鲁斯卡尔(Kruskal)算法

学习最小生成树算法之前我们先来了解下 下面这些概念: 树(Tree):如果一个无向连通图中不存在回路,则这种图称为树. 生成树 (Spanning Tree):无向连通图G的一个子图如果是一颗包含G的所有顶点的树,则该子图称为G的生成树. 生成树是连通图的极小连通子图.这里所谓极小是指:若在树中任意增加一条边,则将出现一条回路:若去掉一条边,将会使之变成非连通图. 最小生成树(Minimum Spanning Tree,MST):或者称为最小代价树Minimum-cost Spanning Tr

无向带权图的最小生成树算法——Prim及Kruskal算法思路

边赋以权值的图称为网或带权图,带权图的生成树也是带权的,生成树T各边的权值总和称为该树的权. 最小生成树(MST):权值最小的生成树. 生成树和最小生成树的应用:要连通n个城市需要n-1条边线路.可以把边上的权值解释为线路的造价.则最小生成树表示使其造价最小的生成树. 构造网的最小生成树必须解决下面两个问题: 1.尽可能选取权值小的边,但不能构成回路: 2.选取n-1条恰当的边以连通n个顶点: MST性质:假设G=(V,E)是一个连通网,U是顶点V的一个非空子集.若(u,v)是一条具有最小权值的

最小生成树(MST)----普里姆(Prim)算法与克鲁斯卡尔(Kruskal)算法

1.概念:给定一个带权的无向连通图,如何选取一棵生成树,使树上所有边上权的总和为最小,这叫最小生成树. 2.应用:例如:要在n个城市之间铺设光缆,主要目标是要使这 n 个城市的任意两个之间都可以通信,但铺设光缆的费用很高,且各个城市之间铺设光缆的费用不同,因此另一个目标是要使铺设光缆的总费用最低.这就需要找到带权的最小生成树. 3.求最小生成树的算法 3.1 普里姆(Prim)算法 方法:从指定顶点开始将它加入集合中,然后将集合内的顶点与集合外的顶点所构成的所有边中选取权值最小的一条边作为生成树

[算法第一轮复习] kruskal求最小生成树算法

最小生成树算法即MST,有kruskal,prim两种算法,这里主要介绍kruskal 什么是最小生成树? 对于一个图,保证其中每个点都可以连通的最小的花费 1.算法核心 贪心+并查集 2.算法实现过程 克鲁斯卡尔算法 假设 WN=(V,{E}) 是一个含有 n 个顶点的连通网,则按照克鲁斯卡尔算法构造最小生成树的过程为:先构造一个只含 n 个顶点,而边集为空的子图,若将该子图中各个顶点看成是各棵树上的根结点,则它是一个含有 n 棵树的一个森林.之后,从网的边集 E 中选取一条权值最小的边,若该

c/c++ 用克鲁斯卡尔(kruskal)算法构造最小生成树

c/c++ 用克鲁斯卡尔(kruskal)算法构造最小生成树 最小生成树(Minimum Cost Spanning Tree)的概念: 假设要在n个城市之间建立公路,则连通n个城市只需要n-1条线路.这时,自然会考虑,如何在最节省经费的前提下建立这个公路网络. 每2个城市之间都可以设置一条公路,相应地都要付出一定的经济代价.n个城市之间,最多可以设置n(n-1)/2条线路,那么,如何在这些可能的线路中选择n-1条,以使总的耗费最少? 克鲁斯卡尔(kruskal)算法的大致思路: 把每条边的权重