查找最小生成树:克鲁斯克尔算法(Kruskal)算法

一、算法介绍

  Kruskal算法是一种用来查找最小生成树的算法,由Joseph Kruskal在1956年发表。用来解决同样问题的还有Prim算法Boruvka算法等。三种算法都是贪心算法的应用。和Boruvka算法不同的地方是,Kruskal 算法在图中存在相同权值的边时也有效。最小生成树是一副连通加权无向图中一棵权值最小生成树(minimum spanning tree,简称MST生成树的权重是赋予生成树的每条边的权重之和。最小生成树具有 (V – 1) 个边,其中 V 是给定图中的顶点数。关于最小生成树,它可以应用在网络设计、NP难题之类的问题,还可以用于聚类分析,还可以间接应用于其他问题。

二、Kruskal算法查找MST的步骤

  1. 按权重的顺序方式来对所有边进行排序
  2. 选择权重最小的边。检查它是否与形成的生成树形成一个循环。如果未形成循环,则包括该边。否则,将其丢弃
  3. 重复步骤2,直到生成树中有(V-1)个边。

  这个算法是贪婪算法。“贪婪的选择”是选择迄今为止不会造成MST成环的最小的权重边。下面来一个例子来理解:

  该图包含9个顶点(V)和14个边(E)。因此,形成的最小生成树将具有(9 – 1)= 8 个边。

  步骤1:每条边按顺序来排序

 1       /**
 2          * 排序后:
 3          * 权重-src-dest
 4          * 1 6 7
 5          * 2 2 8
 6          * 2 5 6
 7          * 4 0 1
 8          * 4 2 5
 9          * 6 6 8
10          * 7 2 3
11          * 7 7 8
12          * 8 0 7
13          * 8 1 2
14          * 9 3 4
15          * 10 4 5
16          * 11 1 7
17          * 14 3 5
18          */

  步骤2+步骤3::利用按权重排好序的边数组,每次选取最小边,并检测是否成环MST不能有环,所以这里涉及一个并查集的概念,并查集是对这个 Kruskal 算法进行优化的。

  1)数组中一个接一个地选取所有边选取边6-7:不形成循环,将其包括在内。

  2)选取边2-8:不形成循环,将其包括在内。

  3)选取边5-6:不形成循环,将其包括在内。

  4)选取边0-1:不形成循环,将其包括在内。

  5)选取边2-5:不形成循环,将其包括在内。

  6)选取边6-8:由于包括该边会导致成环,因此将其丢弃。

  7)选取边2-3:不形成循环,将其包括在内。

  8)选取边7-8:由于包括该边会导致循环,因此请将其丢弃。

  9)选取边0-7不形成循环,将其包括在内。

   10)选取边1-2:由于包括该边会导致循环,因此请将其丢弃。

    11)选取边3-4:不形成循环,将其包括在内。

  由于包含的边数等于(V – 1),因此算法结束。

三、算法代码

并查集:

  在计算机科学中,并查集是一种树型的数据结构,用于处理一些不交集(Disjoint Sets)的合并及查询问题。有一个联合-查找算法union-find algorithm)定义了两个用于此数据结构的操作:

  • Find:确定元素属于哪一个子集。它可以被用来确定两个元素是否属于同一子集。
  • Union:将两个子集合并成同一个集合。

  并查集树是一种将每一个集合以表示的数据结构,其中每一个节点保存着到它的父节点的引用。

  在并查集树中,每个集合的代表即是集合的根节点。“查找”根据其父节点的引用向根行进直到到底树根。“联合”将两棵树合并到一起,这通过将一棵树的根连接到另一棵树的根。实现这样操作的一种方法是:

  查找元素 i 的集合,根据其父节点的引用向根行进直到到底树根:

1     private int find(Subset[] subsets, int i) {
2         if (subsets[i].parent != i)
3             subsets[i].parent = find(subsets, subsets[i].parent);   // 路径压缩,找到最久远的祖先时“顺便”把它的子孙直接连接到它上面
4         return subsets[i].parent;
5     }

  将两组不相交集合 x 和 y 进行并集,找到其中一个子集最父亲的父亲(也就是最久远的祖先),将另外一个子集的最久远的祖先的父亲指向它:

 1     public void union(Subset[] subsets, int x, int y) {
 2         int xroot = find(subsets, x);
 3         int yroot = find(subsets, y);
 4
 5         /* 在高秩树的根下附加秩低树(按秩划分合并) */
 6         if (subsets[xroot].rank < subsets[yroot].rank) {
 7             subsets[xroot].parent = yroot;
 8         } else if (subsets[xroot].rank > subsets[yroot].rank){
 9             subsets[yroot].parent = xroot;
10         } else {    // 当两棵秩同为r的树联合(作并集)时,它们的秩r+1
11             subsets[yroot].parent = xroot;
12             subsets[xroot].rank++;
13         }
14     }

  同时使用路径压缩、按秩(rank)合并优化的程序每个操作的平均时间仅为 O(α (n)),其中α (n) 是 n=f(x)=A(x, x) 的反函数,A 是急速增加的阿克曼函数。因为 α(n) 是其反函数,故 α (n) 在 n 十分巨大时还是小于 5。因此,平均运行时间是一个极小的常数。实际上,这是渐近最优算法。

Kruskal算法

  使用算法的思想来构造MST。

 1     /**
 2      * 使用Kruskal算法构造MST
 3      */
 4     public void kruskalMST() {
 5         Edge[] result = new Edge[V]; // 将存储生成的MST
 6         int e = 0;                   // 用于result[]的索引变量
 7         int i = 0;                   // 用于排序的边缘索引变量
 8         for (i = 0; i < V; ++i) {
 9             result[i] = new Edge();
10         }
11
12         /* 步骤一:对点到点的边的权重进行排序 */
13         Arrays.sort(edges);
14
15         /* 创建V个子集*/
16         Subset[] subsets = new Subset[V];
17         for (i = 0; i < V; i++) {
18             subsets[i] = new Subset();
19         }
20
21         /* 使用单个元素创建V子集 */
22         for (int v = 0; v < V; v++) {
23             subsets[v].parent = v;
24             subsets[v].rank = 0;    // 单元素的树的秩定义为0
25         }
26
27         /* 用于挑选下一个边的索引 */
28         i = 0;
29
30         while (e < V-1) {
31             /* 步骤2:选取最小的边缘, 并增加下一次迭代的索引 */
32             Edge next_edge = edges[i++];
33
34             int x = find(subsets, next_edge.src);
35             int y = find(subsets, next_edge.dest);
36
37             /* 如果包括此边不引起mst成环(树本无环),则将其包括在结果中并为下一个边增加结果索引存下一条边 */
38             /* 这里判断两个元素是否属于一个子集 */
39             if (x != y) {
40                 result[e++] = next_edge;
41                 union(subsets, x, y);
42             }
43             /* 否则丢弃next_edge */
44         }
45
46         /* 打印result[]的内容以显示里面所构造的MST */
47         System.out.println("Following are the edges in the constructed MST");
48         for (i = 0; i < e; ++i) {
49             System.out.println(result[i].src + " -- " + result[i].dest + " == " + result[i].weight);
50         }
51     }

  平均时间复杂度为O (|E|·log |V|),其中 E 和 V 分别是图的边集和点集。

本文源代码:

  1 package algorithm.mst;
  2
  3 import java.util.Arrays;
  4
  5 public class KruskalAlgorithm {
  6     /* 顶点数和边数 */
  7     private int V, E;
  8     /* 所有边的集合 */
  9     private Edge[] edges;
 10
 11     /**
 12      * 创建一个V个顶点和E条边的图
 13      *
 14      * @param v
 15      * @param e
 16      */
 17     public KruskalAlgorithm(int v, int e) {
 18         V = v;
 19         E = e;
 20         edges = new Edge[E];
 21         for (int i = 0; i < e; i++) {
 22             edges[i] = new Edge();
 23         }
 24     }
 25
 26     /**
 27      * 查找元素i的集合(路径压缩)
 28      * 根据其父节点的引用向根行进直到到底树根
 29      *
 30      * @param subsets
 31      * @param i
 32      * @return
 33      */
 34     private int find(Subset[] subsets, int i) {
 35         if (subsets[i].parent != i)
 36             subsets[i].parent = find(subsets, subsets[i].parent);   // 路径压缩,找到最久远的祖先时“顺便”把它的子孙直接连接到它上面
 37         return subsets[i].parent;
 38     }
 39
 40     /**
 41      * 将两组不相交集合x和y进行并集(按秩合并)
 42      * 这个方法找到其中一个子集最父亲的父亲(也就是最久远的祖先),将另外一个子集的最久远的祖先的父亲指向它。
 43      * <p>
 44      *     并查集树的最基础的表示方法,这个方法不会比链表法好,
 45      *     这是因为创建的树可能会严重不平衡。
 46      *     所以采用“按秩合并”来优化。
 47      * </p>
 48      * <p>
 49      *     即总是将更小的树连接至更大的树上。因为影响运行时间的是树的深度,
 50      *     更小的树添加到更深的树的根上将不会增加秩除非它们的秩相同。
 51      *     在这个算法中,术语“秩”替代了“深度”,因为同时应用了路径压缩时秩将不会与高度相同。
 52      * </p>
 53      *
 54      * @param subsets
 55      * @param x
 56      * @param y
 57      */
 58     public void union(Subset[] subsets, int x, int y) {
 59         int xroot = find(subsets, x);
 60         int yroot = find(subsets, y);
 61
 62         /* 在高秩树的根下附加秩低树(按秩划分合并) */
 63         if (subsets[xroot].rank < subsets[yroot].rank) {
 64             subsets[xroot].parent = yroot;
 65         } else if (subsets[xroot].rank > subsets[yroot].rank){
 66             subsets[yroot].parent = xroot;
 67         } else {    // 当两棵秩同为r的树联合(作并集)时,它们的秩r+1
 68             subsets[yroot].parent = xroot;
 69             subsets[xroot].rank++;
 70         }
 71     }
 72
 73     /**
 74      * 使用Kruskal算法构造MST
 75      */
 76     public void kruskalMST() {
 77         Edge[] result = new Edge[V]; // 将存储生成的MST
 78         int e = 0;                   // 用于result[]的索引变量
 79         int i = 0;                   // 用于排序的边缘索引变量
 80         for (i = 0; i < V; ++i) {
 81             result[i] = new Edge();
 82         }
 83
 84         /* 步骤一:对点到点的边的权重进行排序 */
 85         Arrays.sort(edges);
 86
 87         /* 创建V个子集*/
 88         Subset[] subsets = new Subset[V];
 89         for (i = 0; i < V; i++) {
 90             subsets[i] = new Subset();
 91         }
 92
 93         /* 使用单个元素创建V子集 */
 94         for (int v = 0; v < V; v++) {
 95             subsets[v].parent = v;
 96             subsets[v].rank = 0;    // 单元素的树的秩定义为0
 97         }
 98
 99         /* 用于挑选下一个边的索引 */
100         i = 0;
101
102         while (e < V-1) {
103             /* 步骤2:选取最小的边缘, 并增加下一次迭代的索引 */
104             Edge next_edge = edges[i++];
105
106             int x = find(subsets, next_edge.src);
107             int y = find(subsets, next_edge.dest);
108
109             /* 如果包括此边不引起mst成环(树本无环),则将其包括在结果中并为下一个边增加结果索引存下一条边 */
110             /* 这里判断两个元素是否属于一个子集 */
111             if (x != y) {
112                 result[e++] = next_edge;
113                 union(subsets, x, y);
114             }
115             /* 否则丢弃next_edge */
116         }
117
118         /* 打印result[]的内容以显示里面所构造的MST */
119         System.out.println("Following are the edges in the constructed MST");
120         for (i = 0; i < e; ++i) {
121             System.out.println(result[i].src + " -- " + result[i].dest + " == " + result[i].weight);
122         }
123     }
124
125     public static void main(String[] args) {
126         /**
127          * 排序后:
128          * 权重-src-dest
129          * 1 6 7
130          * 2 2 8
131          * 2 5 6
132          * 4 0 1
133          * 4 2 5
134          * 6 6 8
135          * 7 2 3
136          * 7 7 8
137          * 8 0 7
138          * 8 1 2
139          * 9 3 4
140          * 10 4 5
141          * 11 1 7
142          * 14 3 5
143          */
144         int V = 9;
145         int E = 14;
146         KruskalAlgorithm graph = new KruskalAlgorithm(V, E);
147
148         /* 另一个用例的图:
149               1 --- 2 --- 3
150             / |     | \   | 151            0  |     8  \  |  4
152             \ |  /  |   \ | /
153               7 --- 6 --- 5
154          */
155
156         // 添加边 0-1
157         graph.edges[0].src = 0;
158         graph.edges[0].dest = 1;
159         graph.edges[0].weight = 4;
160
161         // 添加边 0-7
162         graph.edges[1].src = 0;
163         graph.edges[1].dest = 7;
164         graph.edges[1].weight = 8;
165
166         // 添加边 1-2
167         graph.edges[2].src = 1;
168         graph.edges[2].dest = 2;
169         graph.edges[2].weight = 8;
170
171         // 添加边 1-7
172         graph.edges[3].src = 1;
173         graph.edges[3].dest = 7;
174         graph.edges[3].weight = 11;
175
176         // 添加边 2-3
177         graph.edges[4].src = 2;
178         graph.edges[4].dest = 3;
179         graph.edges[4].weight = 7;
180
181         // 添加边 2-5
182         graph.edges[5].src = 2;
183         graph.edges[5].dest = 5;
184         graph.edges[5].weight = 4;
185
186         // 添加边 2-8
187         graph.edges[6].src = 2;
188         graph.edges[6].dest = 8;
189         graph.edges[6].weight = 2;
190
191         // 添加边 3-4
192         graph.edges[7].src = 3;
193         graph.edges[7].dest = 4;
194         graph.edges[7].weight = 9;
195
196         // 添加边 3-5
197         graph.edges[8].src = 3;
198         graph.edges[8].dest = 5;
199         graph.edges[8].weight = 14;
200
201         // 添加边 4-5
202         graph.edges[9].src = 4;
203         graph.edges[9].dest = 5;
204         graph.edges[9].weight = 10;
205
206         // 添加边 5-6
207         graph.edges[10].src = 5;
208         graph.edges[10].dest = 6;
209         graph.edges[10].weight = 2;
210
211         // 添加边 6-7
212         graph.edges[11].src = 6;
213         graph.edges[11].dest = 7;
214         graph.edges[11].weight = 1;
215
216         // 添加边 6-8
217         graph.edges[12].src = 6;
218         graph.edges[12].dest = 8;
219         graph.edges[12].weight = 6;
220
221         // 添加边 7-8
222         graph.edges[13].src = 7;
223         graph.edges[13].dest = 8;
224         graph.edges[13].weight = 7;
225
226         graph.kruskalMST();
227
228         /* 用例通过算法得出的MST如下:
229                1    2 -- 3
230              /      | \   231             0       8  \   4
232              \          233                7 -- 6 -- 5
234          */
235     }
236
237     /**
238      * 每条边的信息,实现了{@link Comparable}接口,
239      *      可以使用{@link Arrays}的方法随其边的权重的集合进行自然排序。
240      */
241     class Edge implements Comparable<Edge> {
242         /* 这条边的两个顶点和它的权重 */
243         private int src, dest, weight;
244
245         @Override
246         public int compareTo(Edge o) {
247             return this.weight - o.weight;
248         }
249     }
250
251     /**
252      * 联合查找子集的类
253      */
254     class Subset {
255         /* 其祖先和秩 */
256         private int parent, rank;
257     }
258 }

原文地址:https://www.cnblogs.com/magic-sea/p/11954514.html

时间: 2024-10-14 13:28:40

查找最小生成树:克鲁斯克尔算法(Kruskal)算法的相关文章

最小生成树-Prim算法和Kruskal算法

原文链接:http://www.cnblogs.com/biyeymyhjob/archive/2012/07/30/2615542.html Prim算法 1.概览 普里姆算法(Prim算法),图论中的一种算法,可在加权连通图里搜索最小生成树.意即由此算法搜索到的边子集所构成的树中,不但包括了连通图里的所有顶点(英语:Vertex (graph theory)),且其所有边的权值之和亦为最小.该算法于1930年由捷克数学家沃伊捷赫·亚尔尼克(英语:Vojtěch Jarník)发现:并在195

HDU 1162 Eddy&#39;s picture【最小生成树,Prime算法+Kruskal算法】

Eddy's picture Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Submission(s): 9334    Accepted Submission(s): 4711 Problem Description Eddy begins to like painting pictures recently ,he is sure of himself to

转载:最小生成树-Prim算法和Kruskal算法

本文摘自:http://www.cnblogs.com/biyeymyhjob/archive/2012/07/30/2615542.html 最小生成树-Prim算法和Kruskal算法 Prim算法 1.概览 普里姆算法(Prim算法),图论中的一种算法,可在加权连通图里搜索最小生成树.意即由此算法搜索到的边子集所构成的树中,不但包括了连通图里的所有顶点(英语:Vertex (graph theory)),且其所有边的权值之和亦为最小.该算法于1930年由捷克数学家沃伊捷赫·亚尔尼克(英语:

[转载]最小生成树-Prim算法和Kruskal算法

转载地址:http://www.cnblogs.com/biyeymyhjob/archive/2012/07/30/2615542.html 自己在学,感觉这个讲的很不错,就转载了. Prim算法 1.概览 普里姆算法(Prim算法),图论中的一种算法,可在加权连通图里搜索最小生成树.意即由此算法搜索到的边子集所构成的树中,不但包括了连通图里的所有顶点(英语:Vertex (graph theory)),且其所有边的权值之和亦为最小.该算法于1930年由捷克数学家沃伊捷赫·亚尔尼克(英语:Vo

最小生成树Prim算法和Kruskal算法

Prim算法(使用visited数组实现) Prim算法求最小生成树的时候和边数无关,和顶点树有关,所以适合求解稠密网的最小生成树. Prim算法的步骤包括: 1. 将一个图分为两部分,一部分归为点集U,一部分归为点集V,U的初始集合为{V1},V的初始集合为{ALL-V1}. 2. 针对U开始找U中各节点的所有关联的边的权值最小的那个,然后将关联的节点Vi加入到U中,并且从V中删除(注意不能形成环). 3. 递归执行步骤2,直到V中的集合为空. 4. U中所有节点构成的树就是最小生成树. 方法

最小生成树之算法记录【prime算法+Kruskal算法】

首先说一下什么是树: 1.只含一个根节点 2.任意两个节点之间只能有一条或者没有线相连 3.任意两个节点之间都可以通过别的节点间接相连 4.除了根节点没一个节点都只有唯一的一个父节点 最小生成树就是: 在所有数据满足是一棵树的情况下一条将所有节点都连接起来且长度最短的一条路(因为任意两个节点之间有权值 (相连的两点之间权值为一个具体的数,不相连的两个点之间权值为无穷大)) 下面介绍通用的求最小生成树的两种算法: (1)prime算法: /* * 数组tree[]用来记录最小生成树的节点 * 数组

【图论】【最小生成树】Prim和Kruskal算法

在数据结构的教材上,讲到的图的最小生成树算法有两种,一种是Prim(普利姆)算法,一种是Kruskal(克鲁斯卡尔)算法. 两种算法的生成思路有所不同: Prim算法: 算法思想: 算法思想就是每次找到一个距离生成集合最近的点,加入,然后更新距离剩余点之间的距离,继续迭代. 算法步骤: 1.任意选择一个点作为起点,将该点标记为已加入最小生成树,使用一个数组表示该点已加入,join[i] = 1表示i点已加入集合: 2.将最小生成树集合作为整体,计算到其他未加入该集合的点(jion[i] == 0

权重最小生成树的思想与Kruskal算法

晚上做携程的笔试题,附加题考到了权重最小生成树.OMG,就在开考之前,我还又看过一遍这内容,可因为时间太紧,也从来没有写过代码,就GG了.又吃了眼高手低的亏.这不,就好好总结一下,亡羊补牢. 权重最小生成树问题是指在一棵无向全连接图中找到一个无环子集T,既能将所有的结点连接起来,又具有最小的权重和.     解决问题的核心是每次找到一条安全边加入到边集合A中,使得A仍然是某棵最小生成树的子集.     Kruskal找到安全边的方法是:在所有连接森林中两棵不同树的边里面,找到权重最小的边(u,v

HDU 3371 Connect the Cities 【最小生成树,Prime算法+Kruskal算法】

Connect the Cities Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 17167    Accepted Submission(s): 4335 Problem Description In 2100, since the sea level rise, most of the cities disappear. Tho

最小生成树 Prim算法 Kruskal算法

最小生成树 给定一个无向图,如果它的某个子图中任意两个顶点都互相连通并且是一棵树,那么这棵树就叫做生成树,如果边上有权值,那么使得边权和最小的生成树叫做最小生成树. 常见的求解最小生成树的算法有Kruskal算法和Prim算法,生成树是否存在和图是否连通是等价的,所以假定图是连通的. Prim算法 假设有一棵只包含一个顶点v的数T,然后贪心地选取T和其他顶点之间相连的最小权值的边,并把它加到T中.不断进行这个操作,就可以得到最小生成树了(可用反证法证明) 不使用Heap优化的代码 int eg[