学习最小生成树算法之前我们先来了解下 下面这些概念:

(Tree):如果一个无向连通图中不存在回路,则这种图称为树。

生成树 (Spanning Tree):无向连通图G的一个子图如果是一颗包含G的所有顶点的树,则该子图称为G的生成树。

生成树是连通图的极小连通子图。这里所谓极小是指:若在树中任意增加一条边,则将出现一条回路;若去掉一条边,将会使之变成非连通图。

最小生成树(Minimum Spanning Tree,MST):或者称为最小代价树Minimum-cost Spanning Tree:对无向连通图的生成树,各边的权值总和称为生成树的权,权最小的生成树称为最小生成树。

构成生成树的准则有三条:

<1> 必须只使用该网络中的边来构造最小生成树。

<2> 必须使用且仅使用n-1条边来连接网络中的n个顶点

<3> 不能使用产生回路的边。

构造最小生成树的算法主要有:克鲁斯卡尔(Kruskal)算法和普利姆(Prim)算法他们都遵循以上准则。

接下分别讨论一下这两种算法以及判定最小生成树是否唯一的方法。

克鲁斯卡尔算法

克鲁斯卡尔算法的基本思想是以边为主导地位,始终选择当前可用(所选的边不能构成回路)的最小权植边。所以Kruskal算法的第一步是给所有的边按照从小到大的顺序排序。这一步可以直接使用库函数qsort或者sort。接下来从小到大依次考察每一条边(u,v)。

具体实现过程如下:

<1> 设一个有n个顶点的连通网络为G(V,E),最初先构造一个只有n个顶点,没有边的非连通图T={V,空},图中每个顶点自成一格连通分量。

<2> 在E中选择一条具有最小权植的边时,若该边的两个顶点落在不同的连通分量上,则将此边加入到T中;否则,即这条边的两个顶点落到同一连通分量      上,则将此边舍去(此后永不选用这条边),重新选择一条权植最小的边。

<3> 如此重复下去,直到所有顶点在同一连通分量上为止。

下面是伪代码:

 1 // 把所有边排序,记第i小的边为e[i] (1<=i<=m)m为边的个数
 2 // 初始化MST为空
 3 // 初始化连通分量,使每个点各自成为一个独立的连通分量
 4
 5 for (int i = 0; i < m; i++)
 6 {
 7     if (e[i].u和e[i].v不在同一连通分量)
 8     {
 9         // 把边e[i]加入MST
10         // 合并e[i].u和e[i].v所在的连通分量
11     }
12 } 

上面的伪代码,最关键的地方在于“连通分量的查询合并”,需要知道任意两个点是否在同一连通分量中,还需要合并两个连通分量。

这个问题正好可以用并查集完美的解决(不得不佩服前辈们聪明才智啊!)

并查集(Union-Find set)这个数据结构可以方便快速的解决这个问题。基本的处理思想是:初始时把每个对象看作是一个单元素集合;然后依次按顺序读入联通边,将连通边中的两个元素合并。在此过程中将重复使用一个搜索(Find)运算,确定一个集合在那个集合中。当读入一个连通边(u,v)时,先判断u和v是否在同一个集合中,如果是则不用合并;如果不是,则用一个合并(Union)运算把u、v所在集合合并,使得这两个集合中的任意两个元素都连通。因此并查集在处理时,主要用到搜索合并两个运算。

为了方便并查集的描述与实现,通常把先后加入到一个集合中的元素表示成一个树结构,并用根结点的序号来表示这个集合。因此定义一个parent[n]的数组,parent[i]中存放的就是结点i所在的树中结点i的父亲节点的序号。例如,如果parent[4]=5,就是说4号结点的父亲结点是5号结点。约定:如果i的父结点(即parent[i])是负数,则表示结点i就是它所在的集合的根结点,因为集合中没有结点的序号是负的;并且用负数的绝对值作为这个集合中所含结点的个数。例如,如果parent[7]=-4,说明7号结点就是它所在集合的根结点,这个集合有四个元素。初始时结点的parent值为-1(每个结点都是根结点,只包含它自己一个元素)。

实现Kruskal算法数据结构主要有3个函数。

 1 void UFset() // 初始化
 2 {
 3     for (int i = 0; i < n; i ++)
 4         parent[i] = -1;
 5 }
 6 int Find(int x)  // 查找并返回结点x所属集合的根结点
 7 {
 8     int s;    // 查找位置
 9     for (s = x; parent[s]>=0; s = parent[s]);  // 注意这里的 ;
10     while (s != x)   // 优化方案 -- 压缩路径,使后续的查找
11     {
12         int tmp = parent[x];
13         parent[x] = s;
14         x = tmp;
15     }
16     return s;
17 }
18 // R1和R2是两个元素,属于两个不同的集合,现在合并这两个集合
19 void Union (int R1, int R2)
20 {
21     // r1位R1的根结点,r2位R2的根结点
22     int r1 = Find(R1), r2 = Find(R2);
23     int tmp = parent[r1] + parent[r2];   // 两个集合的结点个数之和(负数)
24     // 如果R2所在树结点个数 > R1所在树结点个数
25     // 注意parent[r1]和parent[r2]都是负数
26     if(parent[r1] > parent[r2])    // 优化方案 -- 加权法则
27     {
28         parent[r1] = r2;        // 将根结点r1所在的树作为r2的子树(合并)
29         parent[r2] = tmp;       // 跟新根结点r2的parent[]值
30     }
31     else
32     {
33         parent[r2] = r1;         // 将根结点r2所在的树作为r1的子树(合并)
34         parent[r1] = tmp;        // 跟新根结点r1的parent[]值
35     }
36 } 

接下来对 Find 函数和 Union 函数的实现过程作详细解释。

Find 函数:在 Find 函数中如果仅仅靠一个循环来直接得到结点所属集合的根结点的话,通过多次的 Union 操作就会有很多结点在树的比较深层次中,再查找起来就会很费时。可以通过压缩路径来加快后续的查找速度:增加一个 While 循环,每次都把从结点 x 到集合根结点的路径上经过的结点直接设置为根结点的子女结点。虽然这增加了时间,但以后的查找会更快。如图 3.4 所示,假设从结点 x = 6 开始压缩路径,则从结点 6 到根结点1 的路径上有 3 个结点:6、10、8,压缩后,这 3 个结点都直接成为根结点的子女结点,如图(b)所示。

并查集:Find函数中的路径压缩

Union 函数:两个集合并时,任一方可做为另一方的子孙。怎样来处理呢,现在一般采用加权合并,把两个集合中元素个数少的根结点做为元素个数多的根结点的子女结点。这样处理有什么优势呢?直观上看,可以减少树中的深层元素的个数,减少后续查找时间。

例如,假设从 1 开始到 n,不断合并第 i 个结点与第 i+1 个结点,采用加权合并思路的过程如下图所示(各子树根结点上方的数字为其 parent[ ]值)。这样查找任一结点所属集合的时间复杂度几乎都是 O(1)!!!

并查集:加权合并

不用加权规则可能会得到下图所示的结果。这就是典型的退化树(只有一个叶结点,且每个非叶结点只有一个子结点)现象,再查找起来就会很费时,例如查找结点 n 的根结点时复杂度为 O(n)。

并查集:合并时不加权的结果。

例 利用 Kruskal 算法求无向网的最小生成树,并输出依次选择的各条边及最终求得的最小生成树的权。

假设数据输入时采用如下的格式进行输入:首先输入顶点个数 n 和边数 m,然后输入 m 条边的数据。每条边的数据格式为:u v w,分别表示这条边的两个顶点及边上的权值。顶点序号从 1开始计起。

分析:

在下面的代码中,首先读入边的信息,存放到数组 edges[ ]中,并按权值从小到大进行排序。

Kruskal( )函数用于实现 :首先初始化并查集,然后从 edges[ ]数组中依次选用每条边,如果这条边的两个顶点位于同一个连通分量,则要弃用这条边;否则合并这两个顶点所在的连通分量。

代码如下:

 1 #include <stdio.h>
 2 #include <string.h>
 3 #include <algorithm>
 4 #define MAXN 11  //顶点个数的最大值
 5 #define MAXM 20  //边的个数的最大值
 6 using namespace std;
 7
 8 struct edge  //边
 9 {
10     int u, v, w; //边的顶点、权值
11 }edges[MAXM]; //边的数组
12
13 int parent[MAXN];  //parent[i]为顶点 i 所在集合对应的树中的根结点
14 int n, m;  //顶点个数、边的个数
15 int i, j;  //循环变量
16 void UFset( )  //初始化
17 {
18     for( i=1; i<=n; i++ )
19         parent[i] = -1;
20 }
21 int Find( int x ) //查找并返回节点 x 所属集合的根结点
22 {
23     int s; //查找位置
24     for( s=x; parent[s]>=0; s=parent[s] );
25     while( s!=x ) //优化方案―压缩路径,使后续的查找操作加速。
26     {
27         int tmp = parent[x];
28         parent[x] = s;
29         x = tmp;
30     }
31     return s;
32 }
33
34 //将两个不同集合的元素进行合并,使两个集合中任两个元素都连通
35 void Union( int R1, int R2 )
36 {
37     int r1 = Find(R1), r2 = Find(R2); //r1 为 R1 的根结点,r2 为 R2 的根结点
38     int tmp = parent[r1] + parent[r2]; //两个集合结点个数之和(负数)
39     //如果 R2 所在树结点个数 > R1 所在树结点个数(注意 parent[r1]是负数)
40     if( parent[r1] > parent[r2] ) //优化方案――加权法则
41     {
42         parent[r1] = r2;
43         parent[r2] = tmp;
44     }
45     else
46     {
47         parent[r2] = r1;
48         parent[r1] = tmp;
49     }
50 }
51 bool cmp( edge a, edge b ) //实现从小到大排序的比较函数
52 {
53     return a.w <= b.w;
54 }
55 void Kruskal( )
56 {
57     int sumweight = 0;  //生成树的权值
58     int num = 0;  //已选用的边的数目
59     int u, v;  //选用边的两个顶点
60     UFset( ); //初始化 parent[]数组
61     for( i=0; i<m; i++ )
62     {
63         u = edges[i].u; v = edges[i].v;
64         if( Find(u) != Find(v) )
65         {
66             printf( "%d %d %d\n", u, v, edges[i].w );
67             sumweight += edges[i].w; num++;
68             Union( u, v );
69         }
70         if( num>=n-1 ) break;
71     }
72     printf( "weight of MST is %d\n", sumweight );
73 }
74 int main( )
75 {
76     int u, v, w; //边的起点和终点及权值
77     scanf( "%d%d", &n, &m ); //读入顶点个数 n
78     for( int i=0; i<m; i++ )
79     {
80     scanf( "%d%d%d", &u, &v, &w ); //读入边的起点和终点
81     edges[i].u = u; edges[i].v = v; edges[i].w = w;
82     }
83     sort(edges,edges+m,cmp);
84     Kruskal();
85     return 0;
86 }

View Code

转载于:https://www.cnblogs.com/yoke/p/6697013.html

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

  1. 算法:通过克鲁斯卡尔(Kruskal)算法,求出图的最小生成树

    之前我给大家分享过用普利姆(Prim)算法来求出图的最小生成树(点我去看看),今天我再给大家分享一个也是求图的最小生成树的克鲁斯卡尔(Kruskal)算法 克鲁斯卡尔(Kruskal)算法,就相当于先 ...

  2. 数据结构与算法(7-3)最小生成树(普里姆(Prim)算法和克鲁斯卡尔(Kruskal)算法)

    目录 一.最小生成树简介 二.普里姆算法(Prim) 1.原理 2.存储 2-1.图顶点和权: 2-3. 最小生成树: 3.Prim()函数 3-1.新顶点入树 3-2.保留最小权 3-3. 找到最小 ...

  3. 对下图所示的连通网络G,用克鲁斯卡尔(Kruskal)算法求G的最小生成树T,请写出在算法执行过程中,依次加入T的边集TE中的边。说明该算法的基本思想及贪心策略,并简要分析算法的时间复杂度

    对下图所示的连通网络G,用克鲁斯卡尔(Kruskal)算法求G的最小生成树T,请写出在算法执行过程中,依次加入T的边集TE中的 边.说明该算法的基本思想及贪心策略,并简要分析算法的时间复杂度

  4. 【算法】克鲁斯卡尔 (Kruskal) 算法

    目录 1.概述 2.代码实现 2.1.并查集 2.2.邻接矩阵存储图 2.3.邻接表存储图 2.4.测试代码 3.应用 本文参考: <数据结构教程>第 5 版 李春葆 主编 1.概述 (1 ...

  5. 算法之克鲁斯卡尔(Kruskal)算法

    克鲁斯卡尔(Kruskal)算法 克鲁斯卡尔(Kruskal)算法,是用来求加权连通图的最小生成树的算法. 基本思想:按照权值从小到大的顺序选择n-1条边,并保证这n-1条边不构成回路 具体做法:首先 ...

  6. 普里姆(Prim)算法和克鲁斯卡尔(Kruskal)算法

    图是一种基础又重要的数据结构,图的生成树是图的一个极小连通子图.最小生成树是无向连通网的所有生成树中边的权值之和最小的一棵生成树.求图的最小生成树可以牵引出很多经典的题目,例如在N个城市之间建立通讯网 ...

  7. Java实现之克鲁斯卡尔(Kruskal)算法

    一.问题引入 1.问题引入 1)某城市新增7个站点(A,B,C,D,E,F,G),现在需要修路把7个站点连通 2)各个站点的距离用边线表示(权),比如A-B距离12公里 3)问:如何修路保证各个站点都 ...

  8. 【数据结构与算法】克鲁斯卡尔(Kruskal)算法

    一,应用场景 公交站问题 1)某城市新增7个站点(A,B,C,D,E,F,G),现在需要修路把7个站点连通 2)各个站点的距离用边线表示(权),比如 A - B距离12公里 3)问:如何修路保证各个站 ...

  9. 普里姆算法(Prim)和克鲁斯卡尔(Kruskal)算法

    普里姆算法(Prim)和克鲁斯卡尔(Kruskal)算法 普里姆算法的基本思想: 取图中任意一个顶点 v 作为生成树的根,之后往生成树上添加新的顶点 w.添加顶点w的条件为:w 和已在生成树上的顶点v ...

  10. 克鲁斯卡尔算法c语言,最小生成树-克鲁斯卡尔(Kruskal)算法

    1. 克鲁斯卡尔算法简介 克鲁斯卡尔算法是一种用来寻找最小生成树的算法(用来求加权连通图的最小生成树的算法).在剩下的所有未选取的边中,找最小边,如果和已选取的边构成回路,则放弃,选取次小边. 而具体 ...

最新文章

  1. 基于android的家庭财务通 .apk,毕业论文(设计)基于Android的家庭财务管家的设计与实现.doc...
  2. 昔日的独角兽Docker资金紧张,未来前途未卜
  3. SSDT表与ShadowSSDT表
  4. opencv python 将RGB表示的图像转换为HSV(或Ycbcr)表示
  5. [C++调试笔记]初始化3种粒子数据initmaxw
  6. Intellij IDEA 2019 自动生成 serialVersionUID
  7. Solr及Spring-Data-Solr入门学习
  8. net user 用户名 密码 /add 建立用户
  9. java 查找排序_查找与排序算法(Java实现)
  10. neo4j3.0 java使用_neo4j-java连接
  11. Android开发中的单元测试
  12. python语言程序设计教程课后答案刘卫国_Visual FoxPro程序设计教程(主编:刘卫国 第三版)1-5课后答案...
  13. CBoard框架使用总结五--扩展菜单配置功能
  14. 酒店后台管理系统、客栈管理、入住会员、房间管理、房源、房型、订单、报表、酒店企业、短信模板、积分、打印、交接班、住宿、入住、锁房、收支流水、房间销售、消费项目、酒店管理、渠道销售、支付管理、连锁酒店
  15. vue当前浏览器是否为ie_vue项目检测IE浏览器版本,版本太低给出提示
  16. 【Net Core系列】(前言).net core学习笔记
  17. DP-900认证考试攻略
  18. c++ 输出正三角形图形
  19. 计算机鼠标知识,计算机基本组成及键盘鼠标知识.ppt
  20. Android ScrollView滚动区高度和子LinearLayout的layout_marginTop的关系

热门文章

  1. nmake构建Geos库
  2. 蒙版遮住全屏(包括导航栏)
  3. 全局变量引起的BUG
  4. oracle数据库自动备份
  5. HTTPS加密那点事--轻松秒懂HTTPS非对称加密
  6. eclipse——jsp字体设置
  7. merge into语句的使用
  8. java兩個矩陣相乘_Day12 練習java-矩陣乘法
  9. POJ3762 时间段用k次
  10. 计算机网络-UDP协议