最小生成树

引论:研究最小生成树之前,我们还是先搞清楚什么是生成树。子图包含原图的所有顶点且边数等于顶点数减去一,并且要求子图不产生回路。

总结起来就三点:1.包含图所有顶点。2.边个数等于顶点个数减去一。3.围成的新图不能产生回路(就是树了)

概念是死板的,我来画图演示。

根据概念,我们知道生成树的是不唯一的,我们列举出3种生成树。

深度优先遍历

![在这里插入图片描述](https://img-blog.csdnimg.cn/20210325192337181.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0R6cDE5OTkwMw==,size_16,color_FFFFFF,t_70#pic_center)

忘记深度优先遍历可以去看看我写的深度遍历算法,此时利用深度遍历所有顶点就可以构成生成树。

广度优先遍历

![在这里插入图片描述](https://img-blog.csdnimg.cn/20210325192349688.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0R6cDE5OTkwMw==,size_16,color_FFFFFF,t_70#pic_center)

自己手动随便构造

好了,看了上面的三种生成树,我们就可以讲解我们的最小生成树了,最小生成树就是在构造生成树时,原图的边是带权值的,我们构造的生成树权值之和要求最小,此时的生成树就是最小生成树。

最小生成树主要有两类经典算法,一个是普利姆算法,一个是克鲁斯卡尔算法

Prime算法

Prime算法直接口述,你可能会懵逼,且不好表述,直接上它的算法步骤吧。

  1. 随便选择一个顶点作为起点,然后设置数组dis,存储其余点到起点的距离,自己到自己距离0,两个点之间没有直接边相连的距离是无穷大
  2. 经过N此操作(N=顶点数-1)
    1. 选择一个未被选择的点k,且dis[k]是当前dis数组最小的
    2. 标记点k被选择
    3. 以k为中介点,但凡未被访问点x点k的距离小于之前dis[x]的距离,就更新dis[x]为点x到点k的距离
  3. 得到最小生成树。

看下面这个过程,大家感受下算法步骤吧
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sbN3zig3-1616671372361)(image/tree22.jpg)]

1.初始化时各个顶点对应下标

顶点 A B C D E
下标 0 1 2 3 4

2.假设我们从A开始.
设置权值数组存储各个点到A点距离.设置前驱下标数组存储各个点前驱下标,初始所有点的前驱下标都是指向A,所以初始化为A下标0.
设置标记点数组,用来标记每个点是否被用,0表示未使用,1代表被使用。

3.0初始的状态,点A是起点,标记被访问

顶点 A B C D E
下标 0 1 2 3 4
权值 0 6 1 4 4
前驱下标 0 0 0 0 0
标记点 1 0 0 0 0

3.1此时权值数组最小的权值是1,(每次都是从未被访问点中找最小权值)对应的点是C,所以第一次选择点C,c的前驱下标是0,代表它前驱是A,所以选择A-C边,修改C点被标记,设置标记点是1,然后以C点位基础,逐个遍历未被访问点,首先是B,点C到点B的权值是无穷,所以不小于权值6,不修改,接着是点D,点C到点D的权值是8,大于权值数组值4,也不修改。接着是点E,点E到点C的距离也是无穷大,也不修改,结果如下表

顶点 A B C D E
下标 0 1 2 3 4
权值 0 6 1 4 4
前驱下标 0 0 0 0 0
标记点 1 0 1 0 0

3.2继续选择权值数组里最小的是4,对应的是d和e,随便选择一个点,我们选择d,标记d被用,d的前驱下标是0,对应点A,所以打印A-D,接着遍历未被访问点到d点的距离,首先是点B,点B到点D的距离是2,小于当前权值数组的6,修改B对应权值是2,并且修改B的前驱下标是D的下标3。接着是点E,点E到点D的距离是无穷大,不修改。对应结果如下表

顶点 A B C D E
下标 0 1 2 3 4
权值 0 2 1 4 4
前驱下标 0 3 0 0 0
标记点 1 0 1 1 0

3.3继续选择权值数组最小的是2,对应点是b,查看B的前驱下标是3,对应点D,打印B-D标记b被使用,遍历未被访问的点,首先是E,点E到点B的距离是2,修改权值数组E的权值是2,并且修改E的前驱点下标是b的下标1.结果如下表

顶点 A B C D E
下标 0 1 2 3 4
权值 0 2 1 4 2
前驱下标 0 3 0 0 1
标记点 1 1 1 1 0

3.4继续选择权值数组最小的是2,对应点e,e的前驱下标是1,打印b-e,设置E被访问,此时所有点都被访问到,结束

顶点 A B C D E
下标 0 1 2 3 4
权值 0 2 1 4 2
前驱下标 0 3 0 0 1
标记点 1 1 1 1 1

最终我们选择的边依次是a–c,a–d,b–d,b–e;构成了最小生成树

//最下生成树-Prime
void Prim(struct MGraph *g, char obj)
{int index = 0, min, k;//标记数组temp(记录顶点是否被访问)int *temp = (int*)malloc(sizeof(int)*g->numVertes);//距离数组dis(标记当前最短距离)int *dis = (int*)malloc(sizeof(int)*g->numVertes);//前驱数组pre(标记每个点前驱节点)int *pre = (int*)malloc(sizeof(int)*g->numVertes);//寻找起点的下标for (int i = 0; i < g->numVertes; i++) {if (g->vetes[i] == obj){index = i;break;}}printf("%d", index);//初始化数组for (int i = 0; i < g->numVertes; i++){dis[i] = g->data[index][i];temp[i] = 0;//未访问pre[i] = index;//前驱都是起点}temp[index] = 1;for (int i = 1; i < g->numVertes; i++){min = MAX;//找出最小权值的边,并标记点for (int j = 0; j < g->numVertes; j++){if (temp[j] == 0 && dis[j] < min){min = dis[j];k = j;}}//输出边printf("%c-->%c\n", g->vetes[index], g->vetes[k]);//修改dis,temp,pre数组temp[k] = 1;index = k;for (int j = 0; j < g->numVertes; j++){if (temp[j] == 0 && g->data[k][j] < dis[j]){dis[j] = g->data[k][j];pre[j] = k;}}}
}

Krusual算法

Krusual理解起来十分简单,就是将所有边按照权值进行排序(升序),然后从第一个边开始取,每次都判断加入新边之后是否构成回路,当遍历完所有边时,也就生成了最小生成树。

1.首先按照边的权值大小进行排序

编号 起点 终点 权重
1 A C 1
2 B E 2
3 B D 2
4 A D 4
5 A E 4
6 A B 6
7 C D 8

2.逐个边的加入,判断每次加入是否构成回路

  • 加入AC
  • 加入BE
  • 加入BD
  • 加入AD
  • 加入AE时,产生了回路,不加入
  • 加入AB时,产生了回路,不加入
  • 加入CD时,产生了回路,不加入

最后结果如下图(最小生成树)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K6hiFWBx-1616671372363)(image/tree20.jpg)]

问题:如何判断是否构成回路?在手动时,我们可以自己发现是否有回路,但是在计算机中?这里又出现了一个经典的算法,并查集算法。

并查集算法

我来举例子分析,你就知道这个算法的巧妙了。

假设有1-6号的人,每次输入两个随机的编号对应1-6号某两个人,输入n此后,我来给你随机的两个人,你需要判断出这两个人是否认识。例如我输入(1,2)(3,4)(5,6)表示1号与2号认识,3号与4号认识,5号与6号认识。此时我如果给你(3,5),你肯定知道这两个人不认识。

接下来用并查集来计算两个人是否认识。我们输入的序列对依次是(1,3)(5,6)(2,3)(2,5)

  • 初始每个编号的人都是一个树
  • 每次输入两个编号时,先判断这两个编号对应树的根是否一样,一样证明两个人在一棵树上,不一样代表二者不认识,进行合并。

看上面整个过程,最后的树就是一个完整的关系树了,此时我问你3和6是否认识,你只需要判断3和6在一颗树?当然它两就在一棵树上。这个就是并查集算法了,它可以用来判断回路问题。你可能问?为什么可以?算了,还是看下图演示吧。

总结:所以我们利用并查集去每次加入新边时,判断这个边两个顶点是否在一棵树,不在一棵树,就证明加入当前边不构成回路,反之亦然。

//最小生成树-Kruskal
void Kruskal(struct MGraph *g)
{int min = MAX, temp, num = 0,begin,end;struct Edge swap;struct Edge *edge = (struct Edge*)malloc(sizeof(struct Edge)*g->numEdges);struct UFStree *Utree = (struct UFStree*)malloc(sizeof(struct UFStree)*g->numVertes);//初始化edge,Utreefor (int i = 0; i < g->numVertes; i++){Utree[i].high = 0;Utree[i].index = i;Utree[i].parent = i;for (int j = 0; j < g->numVertes; j++){if (i<j && g->data[i][j] != 0 && g->data[i][j] != MAX){edge[num].begin = i;edge[num].end = j;edge[num].w = g->data[i][j];num++;}}}//按照边的权值排序(从小到大)for (int i = 0; i < num; i++){min = edge[i].w;temp = i;for(int j = i+1; j < num; j++){if (edge[j].w < min){min = edge[j].w;temp = j;}}if (temp != i)//值拷贝{swap = edge[i];edge[i] = edge[temp];edge[temp] = swap;}}//进行逐个边的筛选for (int i = 0; i < g->numEdges; i++){begin = edge[i].begin;end = edge[i].end;if (find_Tree(Utree, begin) != find_Tree(Utree, end)){union_Tree(Utree, begin, end);printf("%c-->%c-->%d\n", g->vetes[begin], g->vetes[end],g->data[begin][end]);}}}//并查集的查找--根节点
int find_Tree(struct UFStree *tree, int num)
{if (tree[num].parent == num)//到根了//{return num;}else{return find_Tree(tree, tree[num].parent);}
}//并查集节点的合并
void union_Tree(struct UFStree *tree, int x, int y)
{//先找到各自根,判断高度int xP = find_Tree(tree, x);int yP = find_Tree(tree, y);if (tree[xP].high > tree[yP].high){tree[y].parent = xP;}else if (tree[xP].high < tree[yP].high){tree[x].parent = yP;}else{tree[y].parent = xP;tree[xP].high++;}
}

获取完整代码

我分别用C,C++,JAVA三种主流语言编写了完整代码,有需要的在微信公众号搜索考研稳上岸获取完整代码

数据结构练习(同样适用于考研)

微信小程序搜索计算机刷题

数据结构篇十七:图的最小生成树相关推荐

  1. 看得见的数据结构Android版之数组表(数据结构篇)

    零.前言: 一讲到装东西的容器,你可能习惯于使用ArrayList和数组,你有想过ArrayList和数组的区别吗? Java的类起名字都不是随便乱起的,一般前面是辅助,后面是实质:ArrayList ...

  2. python的类程序的结构_Python程序员学习路径之数据结构篇

    原标题:Python程序员学习路径之数据结构篇 点击标题下「异步图书」可快速关注 在计算机科学中,数据结构是一门进阶性课程,概念抽象,难度较大.Python语言的语法简单,交互性强.用Python来讲 ...

  3. Java入门算法(数据结构篇)丨蓄力计划

    本专栏已参加蓄力计划,感谢读者支持 往期文章 一. Java入门算法(贪心篇)丨蓄力计划 二. Java入门算法(暴力篇)丨蓄力计划 三. Java入门算法(排序篇)丨蓄力计划 四. Java入门算法 ...

  4. java 最小生成树_图的最小生成树(java实现)

    1.图的最小生成树(贪心算法) 我两个算法的输出都是数组表示的,当前的索引值和当前索引对应的数据就是通路,比如parent[2] = 5;即2和5之间有一个通路,第二个可能比较好理解,第一个有点混乱 ...

  5. 图的最小生成树(java实现)

    1.图的最小生成树(贪心算法) 我两个算法的输出都是数组表示的,当前的索引值和当前索引对应的数据就是通路,比如parent[2] = 5;即2和5之间有一个通路,第二个可能比较好理解,第一个有点混乱 ...

  6. 数据结构(19)图的最小生成树算法

    数据结构(19)图的最小生成树算法 前言 普里姆(Prim)算法 克鲁斯卡尔(Kruskal)算法 代码 GraphMtx.h GraphMtx.c Main.c 前言 在有n个顶点的图中,要连接所有 ...

  7. C语言数据结构篇——单循环链表的创建,插入,节点删除,打印等操作

     作者名:Demo不是emo  主页面链接:主页传送门 创作初心:对于计算机的学习者来说,初期的学习无疑是最迷茫和难以坚持的,中后期主要是经验和能力的提高,我也刚接触计算机1年,也在不断的探索,在CS ...

  8. 算法:通过普利姆(Prim)算法,求出图的最小生成树

    请看如下的示例图,该图有 V1-V7 七个顶点,每个顶点之间的距离如图所示: 如果上面的图为七个城市的地理分布图,城市间相连的边上的数字为城市间的距离.我们要在这七个城市里面架设电线,使得每一个城市都 ...

  9. R语言可视化散点图、气泡图、动态气泡图、数据点重合的散点图、数据点计数图、抖动数据点图、基于lm方法或者loess方法拟合数据点之间的趋势关系曲线、自定义数据点的大小、色彩、添加主标题、副标题、题注

    R语言可视化散点图.气泡图.动态气泡图.数据点重合的散点图.数据点计数图.抖动数据点图.基于

最新文章

  1. vue 用key拿对象value_vue对象添加属性(key:value)、显示和删除属性
  2. 包package,权限修饰符
  3. 第二阶段个人冲刺03
  4. MySql中truncate,delete,drop的异同点
  5. 不熟悉产品业务,做不好前端开发!
  6. Oracle数据空间的管理
  7. 剑指offer之把二叉树打印成多行
  8. 5寸照片尺寸_证件照尺寸及更换背景颜色教程
  9. 技术干货大集锦(一)
  10. [百度空间] [转]内存屏障 - MemoryBarrier
  11. 雷锋实验室: 伦敦奥运会手机应用盘点
  12. 亚马逊服务器一键重装系统,如何使用Amazon Alexa轻松设置智能家居设备
  13. 感谢各位iPhone12机主,苹果又高了
  14. 字体凹陷效果html,在PS中,想做凹进去的效果,怎么做?例如文字凹进木板中?...
  15. linux查询当前时间
  16. Android 文件中断续传
  17. 大概是全网最详细的Electron ipc 讲解(三)——定情信物传声筒port
  18. 校园网连不上ipv6问题
  19. 微信小程序不显示base64位图片
  20. 海康威视设备SDK调用,是否支持IP通道的思考

热门文章

  1. ADB 操作命令及用法
  2. 2014-04网易、微软、百度、腾讯、阿里实习生招聘经验与经过
  3. 在线工作坊 | 人工智能之 AI on Azure
  4. python-递增的三元子序列
  5. vue 动态添加click_vue,在模块中动态添加dom节点,并监听
  6. TCP、UDP、SCTP概述
  7. 【Machine Learning 学习笔记】feature engineering中noisy feature的影响
  8. CAD2020下载AutoCAD2020下载安装教程AutoCAD2020中文下载安装方法
  9. 【纯新手】小白的第一次面试经过(字节跳动-懂车帝)
  10. 联想小新 Pad Plus 2023 款评测