写在前面:博主是一位普普通通的19届双非软工在读生,平时最大的爱好就是听听歌,逛逛B站。博主很喜欢的一句话花开堪折直须折,莫待无花空折枝:博主的理解是头一次为人,就应该做自己想做的事,做自己不后悔的事,做自己以后不会留有遗憾的事,做自己觉得有意义的事,不浪费这大好的青春年华。博主写博客目的是记录所学到的知识并方便自己复习,在记录知识的同时获得部分浏览量,得到更多人的认可,满足小小的成就感,同时在写博客的途中结交更多志同道合的朋友,让自己在技术的路上并不孤单。

目录:
1.最小生成树
        生成树概念
        最小生成树
2.普里姆算法【Prim】
        普里姆算法简介
        普里姆算法完整代码实现 (C语言)
        普里姆算法小结
3.克鲁斯卡尔算法【Kruskal】
        克鲁斯卡尔算法简介
        克鲁斯卡尔算法完整代码实现 (C语言)
        克鲁斯卡尔算法小结

1.最小生成树

1.1生成树概念

连通图进行遍历,过程中所经过的边和顶点的组合可看做是一棵普通树,通常称为生成树生成树也理解为包含所有顶点的极小连通子图(极小连通子图后边会讲)

连通图中的生成树必须满足以下 2 个条件:
1.包含连通图中所有的顶点;
2.任意两顶点之间有且仅有一条通路;
因此,连通图的生成树具有这样的特征,即生成树中边的数量 = 顶点数 - 1

1.2最小生成树

对于含有 n 个顶点的连通图来说可能包含有多种生成树,例如图 1 所示:

上图中的连通图和它相对应的生成树,可以用于解决实际生活中的问题:假设 A、B、C 和 D 为 4 座城市,为了方便生产生 活,要为这 4 座城市建立通信。对于 4 个城市来讲,本着节约经费的原则,只需要建立 3 个通信线路即可,就如图 1(b) 中的任意一种方式。 在具体选择采用(b)中哪一种方式时,需要综合考虑城市之间间隔的距离,建设通信线路的难度等各种因素,将这些因素综合 起来用一个数值表示,当作这条线路的权值。

假设通过综合分析,城市之间的权值如图 2(a)所示,对于(b)的方案中,选择权值总和为 7 的两种方案最节约经费。 这就是本节要讨论的最小生成树的问题,简单得理解就是给定一个带有权值的连通图(连通网),如何从众多的生成树中筛选出 权值总和最小的生成树,即为该图的 最小生成树

给定一个连通网,求最小生成树的方法有:普里姆(Prim)算法和克鲁斯卡尔(Kruskal)算法。

2.普里姆算法【Prim】

2.1普里姆算法简介

普里姆算法在找最小生成树时,将顶点分为两类:一类是在查找的过程中已经包含在树中的(假设为 A 类),剩下的是另一类 (假设为 B 类)
对于给定的连通网,起始状态全部顶点都归为 B 类。在找最小生成树时,选定任意一个顶点作为起始点,并将之从 B 类移至 A
类;然后找出 B 类中到 A 类中的顶点之间权值最小的顶点,将之从 B 类移至 A 类,如此重复,直到 B 类中没有顶点为止。 所走过的顶点和边就是该连通图的最小生成树。

例如,通过普里姆算法查找上图的最小生成树的步骤为: 假如从顶点 A 出发,顶点 B、C、D 到顶点 A 的权值分别为 2、4、2,所以,对于顶点 A 来说,顶点 B 和顶点 D 到 A 的 权值最小,假设先找到的顶点 B:

继续分析顶点 C 和 D,顶点 C 到 B 的权值为 3,到 A 的权值为 4;顶点 D 到 A 的权值为 2,到 B 的权值为无穷大(如 果之间没有直接通路,设定权值为无穷大)。所以顶点 D 到 A 的权值最小:

最后,只剩下顶点 C,到 A 的权值为 4,到 B 的权值和到 D 的权值一样大,为 3。所以该连通图有两个最小生成树:

2.2普里姆算法完整代码实现 (C语言)

#include <stdio.h>
#include <stdlib.h>
#define VertexType int
#define VRType int
#define MAX_VERtEX_NUM 20
#define InfoType char #define INFINITY 65535
typedef struct {VRType adj; //对于无权图,用 1 或 0 表示是否相邻;对于带权图,直接为权值。InfoType * info; //弧额外含有的信息指针
}ArcCell,AdjMatrix[MAX_VERtEX_NUM][MAX_VERtEX_NUM];
typedef struct {VertexType vexs[MAX_VERtEX_NUM]; //存储图中顶点数据AdjMatrix arcs; //二维数组,记录顶点之间的关系int vexnum,arcnum; //记录图的顶点数和弧(边)数
}MGraph;
//根据顶点本身数据,判断出顶点在二维数组中的位置
int LocateVex(MGraph G,VertexType v){int i=0;//遍历一维数组,找到变量 vfor (; i<G.vexnum; i++) {if (G.vexs[i]==v) {return i;}}return -1;
}
//构造无向网
void CreateUDN(MGraph* G){scanf("%d,%d",&(G->vexnum),&(G->arcnum));for (int i=0; i<G->vexnum; i++) {scanf("%d",&(G->vexs[i]));}for (int i=0; i<G->vexnum; i++) {for (int j=0; j<G->vexnum; j++) {G->arcs[i][j].adj=INFINITY;G->arcs[i][j].info=NULL;}}for (int i=0; i<G->arcnum; i++) {int v1,v2,w; scanf("%d,%d,%d",&v1,&v2,&w);int m=LocateVex(*G, v1);int n=LocateVex(*G, v2);if (m==-1 ||n==-1) {printf("no this vertex\n");return;}G->arcs[n][m].adj=w;G->arcs[m][n].adj=w;}
}
//辅助数组,用于每次筛选出权值最小的边的邻接点
typedef struct {VertexType adjvex;//记录权值最小的边的起始点VRType lowcost;//记录该边的权值
}closedge[MAX_VERtEX_NUM]; closedge theclose;//创建一个全局数组,因为每个函数中都会使用到
//在辅助数组中找出权值最小的边的数组下标,就可以间接找到此边的终点顶点。
int minimun(MGraph G,closedge close){int min=INFINITY;int min_i=-1;for (int i=0; i<G.vexnum; i++) {//权值为 0,说明顶点已经归入最小生成树中;然后每次和 min 变量进行比较,最后找出最小的。if (close[i].lowcost>0 && close[i].lowcost < min) { min=close[i].lowcost; min_i=i;}}//返回最小权值所在的数组下标return min_i;
}
//普里姆算法函数,G 为无向网,u 为在网中选择的任意顶点作为起始点
void miniSpanTreePrim(MGraph G,VertexType u){//找到该起始点在顶点数组中的位置下标int k=LocateVex(G, u);//首先将与该起始点相关的所有边的信息:边的起始点和权值,存入辅助数组中相应的位置,例如(1,2)边,adjvex 为 0, lowcost 为 6,存入 theclose[1]中,辅助数组的下标表示该边的顶点 2for (int i=0; i<G.vexnum; i++) {if (i !=k) {theclose[i].adjvex=k;theclose[i].lowcost=G.arcs[k][i].adj;}}
//由于起始点已经归为最小生成树,所以辅助数组对应位置的权值为 0,这样,遍历时就不会被选中theclose[k].lowcost=0;
//选择下一个点,并更新辅助数组中的信息for (int i=1; i<G.vexnum; i++) {//找出权值最小的边所在数组下标k=minimun(G, theclose);
//输出选择的路径printf("v%d v%d\n",G.vexs[theclose[k].adjvex],G.vexs[k]);
//归入最小生成树的顶点的辅助数组中的权值设为 0theclose[k].lowcost=0;
//信息辅助数组中存储的信息,由于此时树中新加入了一个顶点,需要判断,由此顶点出发,到达其它各顶点的权值是否比之前记录的权值还要小,如果还小,则更新for (int j=0; j<G.vexnum; j++) {if (G.arcs[k][j].adj<theclose[j].lowcost) {theclose[j].adjvex=k;theclose[j].lowcost=G.arcs[k][j].adj;}}}printf("\n");
}
int main(){MGraph G;CreateUDN(&G); miniSpanTreePrim(G, 1);
}

用下图做例子:

2.3普里姆算法小结

普里姆算法的运行效率只与连通网中包含的顶点数相关,而和网所含的边数无关。所以普里姆算法适合于解决边稠密的网,该算法运行的时间复杂度为:O(n2)
如果连通网中所含边的绸密度不高,则建议使用克鲁斯卡尔算法求最小生成树(下面会讲)。

3.克鲁斯卡尔算法

3.1克鲁斯卡尔算法简介

对于任意一个连通网的最小生成树来说,在要求总的权值最小的情况下,最直接的想法就是将连通网中的所有边按照权值大小进行升序排序,从小到大依次选择。

由于最小生成树本身是一棵生成树,所以需要时刻满足以下两点:

  • 生成树中任意顶点之间有且仅有一条通路,也就是说,生成树中不能存在回路;
  • 对于具有 n 个顶点的连通网,其生成树中只能有 n-1 条边,这 n-1

所以克鲁斯卡尔算法的具体思路是:将所有边按照权值的大小进行升序排序,然后从小到大一一判断,条件为:如果这个边不会与之前选择的所有边组成回路,就可以作为最小生成树的一部分;反之,舍去。直到具有 n 个顶点的连通网筛选出来 n-1 条边为止。筛选出来的边和所有的顶点构成此连通网的最小生成树。

判断是否会产生回路的方法为:在初始状态下给每个顶点赋予不同的标记,对于遍历过程的每条边,其都有两个顶点,判断这两个顶点的标记是否一致,如果一致,说明它们本身就处在一棵树中,如果继续连接就会产生回路;如果不一致,说明它们之间还没有任何关系,可以连接。假设遍历到一条由顶点 A 和 B 构成的边,而顶点 A 和顶点 B 标记不同,此时不仅需要将顶点 A 的标记更新为顶点 B 的标记,还需要更改所有和顶点 A 标记相同的顶点的标记,全部改为顶点 B 的标记。

如下例子,使用克鲁斯卡尔算法找最小生成树的过程为::

首先,在初始状态下,对各顶点赋予不同的标记(用颜色区别),如下图所示:

对所有边按照权值的大小进行排序,按照从小到大的顺序进行判断,首先是(1,3),由于顶点 1 和顶点 3 标记不同,所以 可以构成生成树的一部分,遍历所有顶点,将与顶点 3 标记相同的全部更改为顶点 1 的标记,如下:

其次是(4,6)边,两顶点标记不同,所以可以构成生成树的一部分,更新所有顶点的标记为:

其次是(2,5)边,两顶点标记不同,可以构成生成树的一部分,更新所有顶点的标记为:

然后最小的是(3,6)边,两者标记不同,可以连接,遍历所有顶点,将与顶点 6 标记相同的所有顶点的标记更改为顶点 1 的标记:

继续选择权值最小的边,此时会发现,权值为 5 的边有 3 个,其中(1,4)和(3,4)各自两顶点的标记一样,如果连接会产生回路,所以舍去,而(2,3)标记不一样,可以选择,将所有与顶点 2 标记相同的顶点的标记全部改为同顶点 3 相同的标记:

当选取的边的数量相比与顶点的数量小 1 时,说明最小生成树已经生成。所以最终采用克鲁斯卡尔算法得到的最小生成树为(6)所示

3.2克鲁斯卡尔算法完整代码实现 (C语言)

#include "stdio.h"
#include "stdlib.h"
#define MAX_VERtEX_NUM 20
#define VertexType int
typedef struct edge{VertexType initial;VertexType end;VertexType weight;
}edge[MAX_VERtEX_NUM];
//定义辅助数组
typedef struct {VertexType value;//顶点数据int sign;//每个顶点所属的集合
}assist[MAX_VERtEX_NUM]; assist assists;
//qsort 排序函数中使用,使 edges 结构体中的边按照权值大小升序排序
int cmp(const void *a,const void*b){return ((struct edge*)a)->weight-((struct edge*)b)->weight;
}
//初始化连通网
void CreateUDN(edge *edges,int *vexnum,int *arcnum){printf("输入连通网的边数:\n");scanf("%d %d",&(*vexnum),&(*arcnum));printf("输入连通网的顶点:\n");for (int i=0; i<(*vexnum); i++) {scanf("%d",&(assists[i].value)); assists[i].sign=i;}printf("输入各边的起始点和终点及权重:\n");for (int i=0 ; i<(*arcnum); i++) {scanf("%d,%d,%d",&(*edges)[i].initial,&(*edges)[i].end,&(*edges)[i].weight);}
}
//在 assists 数组中找到顶点 point 对应的位置下标
int Locatevex(int vexnum,int point){for (int i=0; i<vexnum; i++) {if (assists[i].value==point) {return i;}}return -1;
}
int main(){int arcnum,vexnum; edge edges;CreateUDN(&edges,&vexnum,&arcnum);
//对连通网中的所有边进行升序排序,结果仍保存在 edges 数组中qsort(edges, arcnum, sizeof(edges[0]), cmp);
//创建一个空的结构体数组,用于存放最小生成树edge minTree;
//设置一个用于记录最小生成树中边的数量的常量int num=0;
//遍历所有的边for (int i=0; i<arcnum; i++) {//找到边的起始顶点和结束顶点在数组 assists 中的位置int initial=Locatevex(vexnum, edges[i].initial);int end=Locatevex(vexnum, edges[i].end);
//如果顶点位置存在且顶点的标记不同,说明不在一个集合中,不会产生回路if (initial!=-1&& end!=-1&&assists[initial].sign!=assists[end].sign) {//记录该边,作为最小生成树的组成部分minTree[num]=edges[i];
//计数+1 num++;
//将新加入生成树的顶点标记全不更改为一样的for (int k=0; k<vexnum; k++) {if (assists[k].sign==assists[end].sign) {assists[k].sign=assists[initial].sign;}
}
//如果选择的边的数量和顶点数相差 1,证明最小生成树已经形成,退出循环if (num==vexnum-1) {break;  }}}
//输出语句for (int i=0; i<vexnum-1; i++) {printf("%d,%d\n",minTree[i].initial,minTree[i].end);}
return 0;
}

对于下图例子来说

输入和运行结果:

3.3克鲁斯卡尔算法小结

刚刚介绍了求最小生成树之普里姆算法。该算法从顶点的角度为出发点,时间复杂度为 O(n2),更适合与解决边的绸密度更高 的连通网。 本节所介绍的克鲁斯卡尔算法,从边的角度求网的最小生成树,时间复杂度为 O(eloge)。和普里姆算法恰恰相反,更适合于求 边稀疏的网的最小生成树

本篇博客转载C语言中文网

最小生成树(普里姆算法【Prim】与克鲁斯卡尔算法【Kruskal】)相关推荐

  1. Java普利姆算法(Prim)与克鲁斯卡尔算法(Kruskal)

    1.Java普利姆算法(Prim)与克鲁斯卡尔算法(Kruskal) 普利姆算法(Prim)与克鲁斯卡尔算法(Kruskal)求**最小生成树(极小连通子图)**的算法 1.1普利姆算法(Prim) ...

  2. 四、最小生成树——普里姆(Prim)算法

    一个连通图的生成树是一个极小的连通子图,它含有图中全部的顶点,但只有足以构成一棵树的n-1条边,那么我们把构造连通图网的最小代价生成树称为最小生成树(就是n个顶点,用n-1条边全部连接起来,并且使得权 ...

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

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

  4. 普里姆(Prim)求最小生成树

    一.普里姆(Prim)算法 1.基本思想:设G=(V, E)是具有n个顶点的连通网,T=(U, TE)是G的最小生成树, T的初始状态为U={u0}(u0∈V),TE={},重复执行下述操作:在所有u ...

  5. 普里姆(Prim)算法(P算法):修路问题

    1,应用场景-修路问题 如图,此时有7个村庄['A', 'B', 'C', 'D', 'E', 'F', 'G'],现在需要把这7个村庄连通 村庄之间的连接线表示可能修路的图示,权值表示举例 此时,如 ...

  6. 普里姆(Prim)算法

    普里姆(Prim)算法 普里姆(Prim)算法思想 普里姆(Prim)算法是一某个顶点为起点,逐步找各顶点最小权值的边来构建最小生成树. 换一种说法: 从任意一顶点 v0 开始选择其最近顶点 v1 构 ...

  7. 一步一步写算法(之克鲁斯卡尔算法 中)

    一步一步写算法(之克鲁斯卡尔算法 中) 原文:一步一步写算法(之克鲁斯卡尔算法 中) [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com] 前面说到 ...

  8. 算法6:克鲁斯卡尔算法

    目录 1. 应用场景-公交站问题 2. 克鲁斯卡尔算法介绍 3. 克鲁斯卡尔算法图解说明 3.1 克鲁斯卡尔算法图解 3.2 克鲁斯卡尔算法分析 3.3 如何判断是否构成回路 4. 代码实现 1. 应 ...

  9. 最小生成树——普里姆算法和克鲁斯卡尔算法

    最小生成树 用来解决工程中的代价问题. 一:普里姆算法 具体代码用C语言实现如下: typedef int VRType;typedef char InfoType;#define MAX_NAME ...

最新文章

  1. swift 基础学习之属性修饰符
  2. ih5长图如何滑动_长图怎么一键截取?这样做很简单
  3. linux删除grid数据文件,MongoDB进阶系列(13)——GridFS大文件的添加、获取、查看、删除...
  4. CSS 如何影响浏览器元素在文档中的排列?
  5. python爬虫爬取网页图片_Python之多线程爬虫抓取网页图片
  6. 亚马逊kindle设置_亚马逊的新款Kindle Fire平板电脑:极客评论
  7. 高斯核函数 python实现
  8. 教你4种压缩PDF让文档快速变小的方法
  9. 如何在php网站上插入站长统计,如何为自己的Wordpress网站添加站长统计代码 | 泰泰博客...
  10. pvs显示unknown device
  11. html中把图片移动位置不变,css如何定位图片保持位置不变?
  12. NRF51822---开发板介绍(连载1)
  13. 项目中分页查询得实现
  14. HG30-3a型数字式多功能校准仪
  15. C/C++学习笔记-指针数组、数组指针
  16. WPF DataGridTextColumn Visibility
  17. 原来这才是高铁霸座男的真实目的!
  18. 计算机数据库课程设计,药品销售管理系统
  19. 2018中国区块链百强企业第二弹 | 链塔智库
  20. PB中小写金额转换成大写金额(发票类型)

热门文章

  1. 在Winform中播放视频等【DotNet,C#】
  2. Python数据分析很难学?60天就够了!
  3. JavaScript学习(十)
  4. 学JS的心路历程 -函式(三)this
  5. Linux编程 9 (shell类型,shell父子关系,子shell用法)
  6. Tomcat 7 自动加载类及检测文件变动原理
  7. 使用Xamarin实现跨平台移动应用开发
  8. Postgresql 字符串操作函数
  9. CAS单点登录0-原理
  10. iptables与tomcat