(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> 如此重复下去,直到所有顶点在同一连通分量上为止。

下面是伪代码:

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

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

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

并查集(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个函数。

void UFset() // 初始化
{for (int i = 0; i < n; i ++)parent[i] = -1;
}
int Find(int x)  // 查找并返回结点x所属集合的根结点
{int s;    // 查找位置 for (s = x; parent[s]>=0; s = parent[s]);  // 注意这里的 ; while (s != x)   // 优化方案 -- 压缩路径,使后续的查找 {int tmp = parent[x];parent[x] = s;x = tmp;}return s;
}
// R1和R2是两个元素,属于两个不同的集合,现在合并这两个集合
void Union (int R1, int R2)
{// r1位R1的根结点,r2位R2的根结点int r1 = Find(R1), r2 = Find(R2);int tmp = parent[r1] + parent[r2];   // 两个集合的结点个数之和(负数) // 如果R2所在树结点个数 > R1所在树结点个数// 注意parent[r1]和parent[r2]都是负数if(parent[r1] > parent[r2])    // 优化方案 -- 加权法则 {parent[r1] = r2;        // 将根结点r1所在的树作为r2的子树(合并) parent[r2] = tmp;       // 跟新根结点r2的parent[]值 }else{parent[r2] = r1;         // 将根结点r2所在的树作为r1的子树(合并) parent[r1] = tmp;        // 跟新根结点r1的parent[]值 }
}

接下来对 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[ ]数组中依次选用每条边,如果这条边的两个顶点位于同一个连通分量,则要弃用这条边;否则合并这两个顶点所在的连通分量。

代码如下:

#include <stdio.h>
#include <string.h>
#include <algorithm>
#define MAXN 11  //顶点个数的最大值
#define MAXM 20  //边的个数的最大值
using namespace std; struct edge  //边
{int u, v, w; //边的顶点、权值
}edges[MAXM]; //边的数组int parent[MAXN];  //parent[i]为顶点 i 所在集合对应的树中的根结点
int n, m;  //顶点个数、边的个数
int i, j;  //循环变量
void UFset( )  //初始化
{for( i=1; i<=n; i++ ) parent[i] = -1;
}
int Find( int x ) //查找并返回节点 x 所属集合的根结点
{int s; //查找位置for( s=x; parent[s]>=0; s=parent[s] );while( s!=x ) //优化方案―压缩路径,使后续的查找操作加速。{int tmp = parent[x];parent[x] = s;x = tmp;}return s;
}//将两个不同集合的元素进行合并,使两个集合中任两个元素都连通
void Union( int R1, int R2 )
{int r1 = Find(R1), r2 = Find(R2); //r1 为 R1 的根结点,r2 为 R2 的根结点int tmp = parent[r1] + parent[r2]; //两个集合结点个数之和(负数)//如果 R2 所在树结点个数 > R1 所在树结点个数(注意 parent[r1]是负数)if( parent[r1] > parent[r2] ) //优化方案――加权法则{parent[r1] = r2; parent[r2] = tmp;}else{parent[r2] = r1; parent[r1] = tmp;}
}
bool cmp( edge a, edge b ) //实现从小到大排序的比较函数
{return a.w <= b.w;
}
void Kruskal( )
{int sumweight = 0;  //生成树的权值int num = 0;  //已选用的边的数目int u, v;  //选用边的两个顶点UFset( ); //初始化 parent[]数组for( i=0; i<m; i++ ){u = edges[i].u; v = edges[i].v;if( Find(u) != Find(v) ){printf( "%d %d %d\n", u, v, edges[i].w );sumweight += edges[i].w; num++;Union( u, v );}if( num>=n-1 ) break;}printf( "weight of MST is %d\n", sumweight );
}
int main( )
{int u, v, w; //边的起点和终点及权值scanf( "%d%d", &n, &m ); //读入顶点个数 nfor( int i=0; i<m; i++ ){scanf( "%d%d%d", &u, &v, &w ); //读入边的起点和终点edges[i].u = u; edges[i].v = v; edges[i].w = w;}sort(edges,edges+m,cmp);Kruskal();return 0;
}
// 把边e[i]加入MST
10         // 合并e[i].u和e[i].v所在的连通分量
11     }
12 } 

上面的伪代码,最关键的地方在于“连通分量的查询合并

最小生成树------克鲁斯卡尔算法(数据结构)相关推荐

  1. 数据结构图之二(最小生成树--克鲁斯卡尔算法)

    [1]克鲁斯卡尔算法 普里姆算法是以某顶点为起点,逐步找各顶点上最小权值的边来构建最小生成树. 克鲁斯卡尔算法是直接以边为目标去构建. 因为权值是在边上,直接去找最小权值的边来构建生成树也是很自然的想 ...

  2. 最小生成树克鲁斯卡尔算法

    文章目录 一.什么是克鲁斯卡尔? 二.原理 三.代码实现 一.什么是克鲁斯卡尔? 克鲁斯卡尔算法是求连通网的最小生成树的另一种方法.与普里姆算法不同,它的时间复杂度为O(eloge)(e为网中的边数) ...

  3. 贪心算法(Greedy Algorithm)最小生成树 克鲁斯卡尔算法(Kruskal#39;s algorithm)

    克鲁斯卡尔算法(Kruskal's algorithm)它既是古典最低的一个简单的了解生成树算法. 这充分反映了这一点贪心算法的精髓.该方法可以通常的图被表示.图选择这里借用Wikipedia在.非常 ...

  4. 最小生成树(克鲁斯卡尔算法)

    关于克鲁斯卡尔算法他是针对边的.而普里姆算法是针对顶点的. 下面还是用普里姆算法的图. 如下: 因此可以构造边集数组. 如下图所示: 代码如下: int Find(int *parent, int f ...

  5. 最小生成树——克鲁斯卡尔算法

      最小生成树的概念就不说了,本文主要是克鲁斯卡尔算法实现,而且用到了并查集思想.相较于prim算法,克鲁斯卡尔更容易理解:在不形成环的情况下,选取最小的权值边,直到点的个数减一.对于环的判断就使用并 ...

  6. USACO 3.1 Agri-Net 最短网络 (最小生成树)(克鲁斯卡尔算法)

    题意 农民约翰被选为他们镇的镇长!他其中一个竞选承诺就是在镇上建立起互联网,并连接到所有的农场.当然,他需要你的帮助.约翰已经给他的农场安排了一条高速的网络线路,他想把这条线路共享给其他农场.为了用最 ...

  7. java克鲁斯卡尔算法,最小生成树( 克鲁斯卡尔算法)

    最小生成树( 克鲁斯卡尔算法) /* Name: Copyright: Author: Date: 01-12-14 20:17 Description: 最小生成树( 克鲁斯卡尔算法) 关于并查集的 ...

  8. 最小生成树算法普利姆算法和克鲁斯卡尔算法实现

    最小生成树算法: 普里姆算法:顶点集合N,辅助顶点集合S,初始化中,将出发点vi加入S,并从N中删除 1.从顶点集合N中找到一条到集合S最近的边(vi,vj),存储该边,并将vj从N移到S中 2.重复 ...

  9. KruskalAlgorithm(克鲁斯卡尔算法)

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

最新文章

  1. windows中遍历指定文件夹下的所有子文件夹
  2. 算法之组合数学及其算法篇(一) ----- 排列与组合
  3. oracle分区表相关
  4. excel换行按什么键_电脑结束任务按什么键
  5. Java –从列表中删除所有空值
  6. Android模拟器启动3个g,android,模拟器_android 模拟器用3.18的内核无法启动,一直黑屏。,android,模拟器,内核 - phpStudy...
  7. CSS2 及CSS3 在ie浏览器下的支持情况
  8. TensorFlow进阶:CNN对CIFAR10图像分类
  9. excel自动排班表_Excel通用值班表日历版,排班人员自动显示,万年历套用
  10. java--实现j2cache二级缓存
  11. 数据库和数据库管理系统的区别
  12. 两台计算机和两台交换机的VLAN配置,两台交换机如何配置连接?
  13. CNKI知网如何批量下载论文
  14. 北邮计算机基础题目,北邮网络教育学院计算机基础题目.doc
  15. 防呆设计(内容摘录)
  16. ARFoundation之路-人脸检测增强之二
  17. C盘占用空间大如何清理
  18. 网络是如何连接的--读书笔记
  19. “智慧高速公路”建设应重点提升“智慧化的出行服务”
  20. iOS中网络编程长连接

热门文章

  1. Google Nexus 6p 刷机步骤
  2. mysql设置id重新从1开始自增
  3. CodeSoft 打印操作类;能获取到对应的lab变量,记得手动释放资源
  4. HP-UX日常工作整理
  5. declare(declared)
  6. 零基础该怎么去学3D游戏建模?
  7. 大学英语计算机统考机考,大学英语四级考试机考
  8. Arduino 学习笔记 | 单片机控制驱动MAX7219 8*8 LED点阵显示模块
  9. 中国海洋大学第四届朗讯杯高级组 I Cuckoo for Hashing
  10. 基于matlab fdma传输系统设计,基于MATLAB的通信系统设计(本科毕业论文).doc