图论中的最小生成树:Kruskal与Prim算法深入解析

 

                                               🎬慕斯主页修仙—别有洞天

                                              ♈️今日夜电波:アンビバレント—Uru

                                                                0:24━━━━━━️💟──────── 4:02
                                                                    🔄   ◀️   ⏸   ▶️    ☰  

                                      💗关注👍点赞🙌收藏您的每一次鼓励都是对我莫大的支持😍


 

目录

最小生成树

Kruskal算法

如何理解Kruskal算法?

Kruskal算法的实现

Prim算法

如何理解Prim算法?

Prim算法的实现


最小生成树

        最小生成树(Minimum Spanning Tree,简称 MST)是一个无向连通图中包含所有顶点的边的权值之和最小的子图。它有以下特点:

  1. 包含原图所有顶点:最小生成树是一个生成树,因此它必须包含图中的所有顶点。
  2. 边的权值之和最小:在所有的生成树中,最小生成树是边的权值总和最小的那一个。
  3. 是无环的:作为一棵树,最小生成树中不存在环,即任意两个顶点之间有且仅有一条路径相连。

        求解最小生成树的常用算法有两种:

  • Kruskal算法:该算法的基本思想是按照边的权值从小到大的顺序选择边,如果这条边连接的两个顶点不在同一个连通分量中,则加入这条边,直到所有顶点都被包含在内。这个过程中,使用并查集数据结构来判断两个顶点是否属于同一个连通分量是非常有效的。
  • Prim算法:与Kruskal算法不同,Prim算法是一种贪心算法,它从任意一个顶点开始,每一步都选择连接当前已选取顶点集合与未选取顶点集合之间权值最小的边,并将其加入到最小生成树中,直到所有顶点都被包含。

        总的来说,最小生成树的概念在许多领域都有应用,如网络设计、交通规划等,它可以帮助找到成本最低的连接方案。

Kruskal算法

如何理解Kruskal算法?

        Kruskal算法是一种用来寻找最小生成树的贪心算法,它的核心思想是选择边权值之和最小的同时不会形成环路的边来构建最小生成树。(使用的就是全局的贪心策略)具体步骤如下:

  1. 边的排序:将图中的所有边按照权值从小到大进行排序。(这一步我们可以使用到优先级队列)
  2. 初始化并查集:使用并查集数据结构来记录各个顶点的连通性,初始时每个顶点自成一个集合。
  3. 遍历边并检查环路:按权值从小到大的顺序遍历每条边,利用并查集判断这条边连接的两个顶点是否属于不同的集合。如果是,则加入这条边并不会形成环路,因此将其加入到最小生成树中,并将这两个顶点所在的集合合并。
  4. 重复直至完成:重复上述步骤,直到添加了V-1条边(V为图中顶点的数量),此时便形成了包含所有顶点的最小生成树。

        需要注意的是,在应用Kruskal算法时,需要确保图是连通的,因为算法只能处理连通图的最小生成树问题。如果图不是连通的,那么需要先对图进行连通分量的分析,然后对每个连通分量分别求解最小生成树。大致图解如下:

Kruskal算法的实现

        本文的Kruskal算法是在邻接矩阵的基础上进行实现的,邻接矩阵的实现参考上一篇文章,这里先实现边的结构体,给定两个size_t类型表示源地址_srcI以及目标地址_dstI(实际上就是对应顶点的下标),根据模版类型W来存储边的权值_w,并且重载了一个>符,用于后续优先级队列中对于伪函数的使用,实现如下:

		struct Edge{size_t _srci;size_t _dsti;W _w;Edge(size_t srci, size_t dsti, const W& w):_srci(srci), _dsti(dsti), _w(w){}bool operator>(const Edge& e) const{return _w > e._w;}};

        需要注意:前面我们将整个邻接矩阵都已经重新命名了,这样做是为了更好的定义最小生成树,毕竟他本质上也是一个图,并且也是在原来的基础上定义的,我们当然可以使用原来的数据结构:

typedef Graph<V, W, MAX_W, Direction> Self;

        接下来,按照上述的实现步骤一步一步实现:(1)初始化,由于最小生成树对于原图可能只是变小于等于原图,其他实际上都是一样的。因此,我们可以根据原图进行初始化,顶点的存储、顶点同下标的映射都是一样的,只是边需要重新构建!其中MAX_W是作为初始化边的值,默认为INT_MAX。(2)使用优先级队列,以升序的方式对上面我们构建的边结构体进行排序,将原图的所有边都放入优先级队列!(3)选出n-1条边,首先定义一个变量size用于储存有多少条边被选,定义一个totalW用于储存权值,定义一个并查集ufs用于查环。每次遍历都会将优先级队列的顶部值取出,上一篇文章中提到过InSet是用于判断是否为同一根节点(判环)的操作,若不为环则按照邻接矩阵的方法构造边,并且也在并查集中合并在一起。(4)这个循环中不论如何都会将优先级队列中的值全部释放,然后退出循环,最后根据size来判断是否可以形成最小生成树并且返回权值。如下为具体的实现:

		W Kruskal(Self& minTree){size_t n = _vertexs.size();minTree._vertexs = _vertexs;minTree._indexMap = _indexMap;minTree._matrix.resize(n);for (size_t i = 0; i < n; ++i){minTree._matrix[i].resize(n, MAX_W);}priority_queue<Edge, vector<Edge>, greater<Edge>> minque;for (size_t i = 0; i < n; ++i){for (size_t j = 0; j < n; ++j){if (i < j && _matrix[i][j] != MAX_W){minque.push(Edge(i, j, _matrix[i][j]));}}}// 选出n-1条边int size = 0;W totalW = W();UnionFindSet ufs(n);while (!minque.empty()){Edge min = minque.top();minque.pop();if (!ufs.InSet(min._srci, min._dsti)){cout << _vertexs[min._srci] << "->" << _vertexs[min._dsti] << ":" << min._w << endl;minTree._AddEdge(min._srci, min._dsti, min._w);ufs.Union(min._srci, min._dsti);++size;totalW += min._w;}else{cout << "构成环:";cout << _vertexs[min._srci] << "->" << _vertexs[min._dsti] << ":" << min._w << endl;}}if (size == n - 1){return totalW;}else{return W();}}

Prim算法

如何理解Prim算法?

        Prim算法是一种用于求解加权连通无向图中最小生成树的算法。它的核心思想是从一个顶点开始,逐步扩展已选取的顶点集合,直到所有顶点都被包含在内。(使用的就是局部的贪心策略)具体步骤如下:

  1. 选择起始点:从图中任意选择一个顶点作为起始点。
  2. 构建边界集:将起始点加入已选取的顶点集合中,同时维护一个边界顶点集合,这些顶点是已选取顶点集合与未选取顶点集合之间的边界。
  3. 选择轻量级边:在边界集合中选择权值最小的边,将该边以及其连接的未选取顶点加入到已选取集合中。
  4. 更新边界集:更新边界集合,将新加入的顶点作为新的边界顶点。
  5. 重复直至完成:重复步骤3和步骤4,直到所有顶点都被加入到已选取的顶点集合中,此时形成的树即为最小生成树。

        总的来说,Prim算法的关键在于每一步都选择当前最优的选择,即权值最小的边,以此来保证最终得到的是最小生成树。这种贪心策略确保了算法的效率和结果的最优性。大致图解如下:

Prim算法的实现

        由于Prim是由局部逐步扩散到全局的,也就是说他并不会选择到选过的顶点,因此Prim的实现并不需要使用到并查集。对于上面Kruskal提到的Edge以及Self不多阐述。

        由于Prim需要指定一个顶点开始,因此接口相对Kruskal多传了一个顶点。接下来,按照上述的实现步骤一步一步实现:(1)初始化,同Kruskal一样,我们都是使用邻接矩阵来实现的,因此前期初始化是相同的操作,接着,获取对应的顶点下标映射,创建两个bool数组分别用于表示已选取的顶点集合和未选取顶点集合,true表示拥有该顶点,false表示不拥有。最后按照下标映射初始化两个数组,选取的则改为true,未选改为false,完成初始化。(2)使用优先级队列先将开始顶点的边放入队列中,同样设置size和totalW。(3)开始选边,出队列顶的边,通过判断是否在已选取的顶点集合来判断是否可选(可以变相理解为是否成环),如果不在表示为没有选过因此可以选,选取后将已选取的顶点集合和未选取顶点集合分别改变,再根据刚刚选取的点选取他临界的顶点。(4)在这个循环中,可能会出现很多重复的点的情况,但是我们有已选取的顶点集合来判断是否可选,这样不可选的会自动出队列而不被选取,最后根据size来判断是否可以形成最小生成树并且返回权值

        具体实现如下:

		W Prim(Self& minTree, const W& src){size_t srci = GetVertexIndex(src);size_t n = _vertexs.size();minTree._vertexs = _vertexs;minTree._indexMap = _indexMap;minTree._matrix.resize(n);for (size_t i = 0; i < n; ++i){minTree._matrix[i].resize(n, MAX_W);}/*set<int> X;set<int> Y;X.insert(srci);for (size_t i = 0; i < n; ++i){if (i != srci){Y.insert(i);}}*/vector<bool> X(n, false);vector<bool> Y(n, true);X[srci] = true;Y[srci] = false;// 从X->Y集合中连接的边里面选出最小的边priority_queue<Edge, vector<Edge>, greater<Edge>> minq;// 先把srci连接的边添加到队列中for (size_t i = 0; i < n; ++i){if (_matrix[srci][i] != MAX_W){minq.push(Edge(srci, i, _matrix[srci][i]));}}cout << "Prim开始选边" << endl;size_t size = 0;W totalW = W();while (!minq.empty()){Edge min = minq.top();minq.pop();// 最小边的目标点也在X集合,则构成环if (X[min._dsti]){//cout << "构成环:";//cout << _vertexs[min._srci] << "->" << _vertexs[min._dsti] << ":" << min._w << endl;}else{minTree._AddEdge(min._srci, min._dsti, min._w);//cout << _vertexs[min._srci] << "->" << _vertexs[min._dsti] << ":" << min._w << endl;X[min._dsti] = true;Y[min._dsti] = false;++size;totalW += min._w;if (size == n - 1)break;for (size_t i = 0; i < n; ++i){if (_matrix[min._dsti][i] != MAX_W && Y[i]){minq.push(Edge(min._dsti, i, _matrix[min._dsti][i]));}}}}if (size == n - 1){return totalW;}else{return W();}}

 


                        感谢你耐心的看到这里ღ( ´・ᴗ・` )比心,如有哪里有错误请踢一脚作者o(╥﹏╥)o! 

                                       

                                                                        给个三连再走嘛~  

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/760708.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

西井科技与安通控股签署战略合作协议 共创大物流全新生态

2024年3月21日&#xff0c;西井科技与安通控股在“上海硅巷”新象限空间正式签署战略合作框架协议。双方基于此前在集装箱物流的成功实践与资源优势&#xff0c;积极拓展在AI数字化产品、新能源自动驾驶解决方案和多场景应用&#xff0c;以及绿色物流链等领域的深度探索、强强联…

视频号下载助手失效了?如何解决下载视频问题!

在刷短视频的时候难免会遇到部分的视频号视频下载不下来&#xff0c;那我们该如何解决视频号下载问题呢&#xff1f; 视频号下载助手解决方案 视频号下载助手失效分为两种情况! 1、可以解析&#xff0c;但不能下载 根据使用视频号下载助手常见的问题&#xff0c;我们发现会有…

Linux本地部署TeslaMate结合内网穿透实现公网访问内网车辆信息

文章目录 1. Docker部署TeslaMate2. 本地访问TeslaMate3. Linux安装Cpolar4. 配置TeslaMate公网地址5. 远程访问TeslaMate6. 固定TeslaMate公网地址7. 固定地址访问TeslaMate TeslaMate是一个开源软件&#xff0c;可以通过连接特斯拉账号&#xff0c;记录行驶历史&#xff0c;统…

YOLOv3学习

YOLOv3仅使用卷积层&#xff0c;使其成为一个全卷积网络&#xff08;FCN&#xff09;。文章中&#xff0c;作者提出一个新的特征提取网络&#xff0c;Darknet-53。正如其名&#xff0c;它包含53个卷积层&#xff0c;每个后面跟随着batch normalization层和leaky ReLU层。没有池…

【网络原理】HTTP 请求 (Request)详解

文章目录 &#x1f38d;请求格式&#x1f384;认识URL&#x1f338;query string&#x1f338;关于 URL encode &#x1f340;认识 “方法” (method)&#x1f338;GET方法&#x1f338;POST 方法&#x1f338;GET 和 POST 的区别 &#x1f332;认识请求 “报头” (header)&…

权限管理系统-0.5.0

六、审批管理模块 审批管理模块包括审批类型和审批模板&#xff0c;审批类型如&#xff1a;出勤、人事、财务等&#xff0c;审批模板如&#xff1a;加班、请假等具体业务。 6.1 引入依赖 在项目中引入activiti7的相关依赖&#xff1a; <!--引入activiti的springboot启动器…

Git进阶命令-reset

一、reset命令使用场景 有时候我们提交了一些错误的或者不完善的代码&#xff0c;需要回退到之前的某个稳定的版本,面对这种情况有两种解决方法: 解决方法1&#xff1a;修改错误内容&#xff0c;再次commit一次 解决方法2&#xff1a;使用git reset 命令撤销这一次错误的com…

汽车KL15、KL30、ACC的区别

文章目录 前言一、KL30是什么&#xff1f;二、KL15是什么&#xff1f;KL15信号的演变 三、为啥用KL15、KL30呢&#xff1f; 前言 相信刚接触汽车电子的伙伴都会有一个疑惑&#xff0c;什么是KL15?什么是KL30? 内心一脸懵逼…… KL是德语Klemme的缩写&#xff0c;指的是ECU的…

RCE漏洞

RCE漏洞概述 远程命令执行/代码注入漏洞&#xff0c;英文全称为Reote Code/CommandExecute&#xff0c;简称RCE漏洞。PHPJava等Web开发语言包含命令执行和代码执行函数,攻击者可以直接向后台服务器远程执行操作系统命今或者运行注入代码&#xff0c;进而获取系统信息、控制后台…

2023年五级区划省市县乡镇行政村社区边界数据

行政区划数据是重要的基础地理信息数据&#xff0c;根据国家统计局公布的数据&#xff0c;行政区划共分为五级&#xff0c;分别为省级、地级、县级、乡镇/街道级、村/社区级。 该套数据以2020-2023年国家基础地理信息数据中的县区划数据作为矢量基础&#xff0c;辅以高德行政区…

Spring Security源码

WebSecurityConfigurerAdapter已废弃&#xff0c;官方推荐使用HttpSecurity 或WebSecurity。 都继承了SecurityBuilder public interface SecurityBuilder<O> {O build() throws Exception;}亮点&#xff1a;通过这种方式很容易知道知道自己构建的Object HttpSecurit…

Shell脚本学习-if循环

最小化的if语句 无实际用途 if [ ] ;then echo fi 脚本解释 if 判断 [ ] 里面的条件是否成立 后面跟then&#xff0c;代表条件成立 如果在一行则使用分号隔离&#xff08;;&#xff09; 如果不在一行使用则直接在下一行驶入then即可。 如果条件成立则输出echo 后面…

IT管理备考TOGAF10证书有哪些好处?

现今&#xff0c;随着信息技术的快速发展&#xff0c;企业对于高效的IT管理需求日益增长。而TOGAF10证书作为全球公认的企业架构管理标准&#xff0c;成为了IT管理者的必备工具。本文将为您详细介绍TOGAF10证书的好处&#xff0c;以助您更好地了解和利用这一强大的工具。 首先&…

大模型主流微调训练方法总结 LoRA、Adapter、Prefix-tuning、P-tuning、Prompt-tuning 并训练自己的数据集

大模型主流微调训练方法总结 LoRA、Adapter、Prefix-tuning、P-tuning、Prompt-tuning 概述 大模型微调(finetuning)以适应特定任务是一个复杂且计算密集型的过程。本文训练测试主要是基于主流的的微调方法:LoRA、Adapter、Prefix-tuning、P-tuning和Prompt-tuning,并对…

金蝶云星空——单据附件上传

文章目录 概要技术要点代码实现小结 概要 单据附件上传 技术要点 单据附件上传金蝶是有提供标准的上传接口&#xff1a; http://[IP]/K3Cloud/Kingdee.BOS.WebApi.ServicesStub.DynamicFormService.AttachmentUpLoad.common.kdsvc 参数说明 参数类型必填说明FileName字符是…

Vue3+JS:实现进度条拖拽

一、效果 二、代码实现 <template><div class"bar" ref"bar"><div class"slider" :style"Pos" mousedown"mousedown"></div></div> </template> <script setup lang"ts"…

8.2K star!史上最强Web应用防火墙

&#x1f6a9; 0x01 介绍 长亭雷池SafeLine是长亭科技耗时近 10 年倾情打造的WAF(Web Application Firewall)&#xff0c;一款敢打出口号 “不让黑客越雷池一步” 的 WAF&#xff0c;我愿称之为史上最强的一款Web应用防火墙&#xff0c;足够简单、足够好用、足够强的免费且开源…

60 个深度学习教程:包含论文、实现和注释 | 开源日报 No.202

labmlai/annotated_deep_learning_paper_implementations Stars: 44.0k License: MIT annotated_deep_learning_paper_implementations 是一个包含深度学习论文的 60 个实现/教程&#xff0c;附带并排注释&#xff1b;包括 transformers&#xff08;原始、xl、switch、feedbac…

Spring MVC(二)-过滤器与拦截器

过滤器和拦截器在职责和使用场景上存在一些差异。 过滤器 拦截器 作用 对请求进行预处理和后处理。例如过滤请求参数、设置字符编码。 拦截用户请求并进行相应处理。例如权限验证、用户登陆检查等。 工作级别 Servlet容器级别&#xff0c;是Tomcat服务器创建的对象。可以…

2024-3-21 市场情绪,嘿嘿嘿

市场的预期终于来到了今天&#xff0c;艾艾精工 13追平了 克来机电 13 &#xff0c;永悦科技8 追平了 睿能科技 8&#xff0c;那么早盘kimi概念卡了1个钟的流动性感觉强度一般般&#xff0c;唯一亮点就是 中广天泽 竞价抢筹&#xff1b;kimi概念本身没有什么大的预期&#xf…