【高阶数据结构(三)】图的遍历最小生成树问题

💓博主CSDN主页:杭电码农-NEO💓

⏩专栏分类:高阶数据结构专栏⏪

🚚代码仓库:NEO的学习日记🚚

🌹关注我🫵带你学习更多Go语言知识
  🔝🔝


在这里插入图片描述

高阶数据结构

  • 1. 前言
  • 2. 图的遍历
  • 3. 图的广度优先遍历
  • 4. 图的深度优先遍历
  • 5. 图的最小生成树
  • 6. Kruskal算法讲解
  • 7. prim算法讲解
  • 8. 总结以及拓展

1. 前言

如果你还不知道什么是图论,以及关于图的存储结构和一些专有名词, 请先阅读这篇文章: 初识图论.

本章重点:

本篇文章着重讲解图的两种遍历方式: 深度优先遍历和广度优先遍历. 并会模拟实现这两种遍历方法. 其次,会讲解关于图的最小生成树的概念,以及关于最小生成树的两个算法: Kruskal算法和Prim算法


2. 图的遍历

给定一个图G和其中任意一个顶点v0,从v0出发,沿着图中各边访问图中的所有顶点,且每个顶点仅被遍历一次。

在这里插入图片描述

这道面试题就是典型的图论的遍历


3. 图的广度优先遍历

正如其名, 广度优先遍历就是先遍历完一个顶点的所有相邻顶点

在这里插入图片描述

比如现在要找东西,假设有三个抽屉,东西在那个抽屉不清楚,现在要将其找到,广度优先遍历的做法是:

  1. 先将三个抽屉打开,在最外层找一遍
  2. 将每个抽屉中红色的盒子打开,再找一遍
  3. 将红色盒子中绿色盒子打开,再找一遍直到找完所有的盒子

注意:每个盒子只能找一次,不能重复找

具体到一个图中就是这样的:

在这里插入图片描述

广度优先遍历看似很简单, 对于顶点A来说, 先走B,C,D. 然后再看B顶点, 走A,C,E, 但是A和C已经走过了,所以不能再此将它们算进去. 做法很简单, 使用一个队列和一个数组, 数组用来存储一个顶点是否已经入过队列了. 对于队列而言, 它的功能相信我不说大家也能明白: 最开始只有A在队列中, A出队, BCD入队, B出队, AC不用入队, 只入E. C出队…

话不多说,直接上代码:

void BFS(const V& src) //图的广度优先遍历
{size_t srci = _index[src];//找到值src对应在数组中的下标queue<int> q;vector<bool> check(_vertex.size(), false); //用来标记哪些元素已经入过队列了q.push(srci);check[srci] = true;while (!q.empty()){int front = q.front();cout << front << ": " << _vertex[front] << endl;q.pop();//把这个顶点的朋友带入队列,并更新check数组for (int i = 0; i < _vertex.size(); i++){//_edge[front][i]代表front->i是否有边,数组值不等于MAX_W就代表它们之间直接相连if (_edge[front][i] != MAX_W && check[i] == false){check[i] = true;q.push(i);}}}
}

注意,此函数是在graph类中实现的成员函数, 其中使用到了成员变量


4. 图的深度优先遍历

正如其名, 深度优先遍历就是先一条路走到底

在这里插入图片描述
比如现在要找东西,假设有三个抽屉,东西在那个抽屉不清楚,现在要将其找到,广度优先遍历的做法是:

  1. 先将第一个抽屉打开,在最外层找一遍
  2. 将第一个抽屉中红盒子打开,在红盒子中找一遍
  3. 将红盒子中绿盒子打开,在绿盒子中找一遍
  4. 递归查找剩余的两个盒子

深度优先遍历:将一个抽屉一次性遍历完(包括该抽屉中包含的小盒子),再去递归遍历其他盒子

具体到一个图中就是这样:

在这里插入图片描述

如果之前学习过递归,回溯算法的同学,相信深度优先遍历对你来说也不是什么难题, 下面就直接上手写代码了!

void DFS(const V& src)//图的深度优先遍历
{size_t srci = _index[src];vector<bool> check(_vertex.size(), false);_DFS(srci, check);
}
void _DFS(size_t srci, vector<bool>& check)
{cout << srci << ": " << _vertex[srci] << endl;check[srci] = true;for (int i = 0; i < _vertex.size(); i++)//遍历与此点相邻的所有点if (_edge[srci][i] != MAX_W && check[i] == false)_DFS(_vertex[i], check);
}

5. 图的最小生成树

若连通图由n个顶点组成,则其生成树必含n个顶点和n-1条边。因此构造最小生成树的准则有三条:

  1. 只能使用图中的边来构造最小生成树
  2. 只能使用恰好n-1条边来连接图中的n个顶点
  3. 选用的n-1条边不能构成回路

最小生成树其实就是子图是最简单的连通图, n-1条边刚好可以连接n个顶点

一个图

每一个图的最小生成树是不唯一的. 一般通过Kruskal算法和prim算法来构造最小生成树


6. Kruskal算法讲解

首先构造一个由这n个顶点组成、不含任何边的图G={V,NULL},其中每个顶点自成一个连通分量,其次不断从E中取出权值最小的一条边(若有多条任取其一),若该边的两个顶点来自不同的连通分量,则将此边加入到G中。如此重复,直到所有顶点在同一个连通分量上为止。

核心:每次迭代时,选出一条具有最小权值,且两端点不在同一连通分量上的边,加入生成树

在这里插入图片描述

克鲁斯卡尔算法的核心就是每次都找最小的权值边, 只要这次选的边没有构成环, 就接着往下走. 比如此图中先选1,再选2,再选2,再选4,依次向后选. 可是问题是, 我怎么知道我选出来的边是否成环了. 这里就需要使用到之前的数据结构: 并查集. 即如果一条边关联的两个点在同一集合, 那么他们就成环了, 反之则不成环. 还有一点, 怎样知道图中哪条边的权值最小? 这里可以使用优先级队列

代码案例:

typedef Graph<V, W, MAX_W, Direction> Self;
struct EDGE //仿函数用于优先级队列
{int _srci;int _desti;W _w;EDGE(int srci, int desti, W w) :_srci(srci), _desti(desti), _w(w){}bool operator>(const EDGE& e) const{return _w > e._w;}
};
W Kruskal(Self& mintree)克鲁斯卡尔算法,返回权重总和
{int n = _vertex.size();mintree._vertex = _vertex;//最开始传入的最小生成树是没有初始化的,没有点和边的关系,若直接使用add会报错mintree._edge.resize(n);mintree._index = _index;for (int i = 0; i < n; i++)mintree._edge[i].resize(n, MAX_W);priority_queue<EDGE, vector<EDGE>, greater<EDGE>> minqueue;//利用优先级队列来存储边的权重大小for (int i = 0; i < n; i++)for (int j = 0; j < n; j++)if (i < j && _edge[i][j] != MAX_W)//i<j,只用走一半,不然如果是有向图,可能会重复插入边minqueue.push(EDGE(i, j, _edge[i][j]));//将所有的边都插入优先级队列int minsize = 0;//选出n-1条边UnionFindSet ufs(n);//利用并查集来判断生成树中是否有环W totalvalue = W();//最后的返回结果while (!minqueue.empty()){EDGE min = minqueue.top();minqueue.pop();if (!ufs.SameSet(min._srci, min._desti))//如果这条边关联的两个点不在一个集合,再继续下面的操作{cout << _vertex[min._srci] << "->" << _vertex[min._desti] << ":" << min._w << endl;mintree._AddEdge(min._srci, min._desti, min._w);//增加一条边ufs.Union(min._srci, min._desti);//将这条边的两个顶点加入同一集合minsize++;//边数一直要到n-1totalvalue += min._w;//最后的结果也需要更新}}if (minsize == n - 1) return totalvalue;else return W();
}

可能大家是第一次见这样写函数, 传入一个空的图, 由于图论的复杂性和算法的特别,所以使用这种方式已经是很优解了,关于代码的解释都在注释中,若还有不懂,欢迎私信


7. prim算法讲解

prim算法的思想是,既然最小生成树包含所有的顶点. 所以我可以从任意顶点开始, 一直沿着此点向外做拓展,直到覆盖了图中所有的顶点. 算法每一步在连接集合A和A之外的结点的边中, 选择一条权值最小的加入A集合, 下图是步骤图,可以参考一下:

在这里插入图片描述

可以看见算法的每一步都在选择,当前集合A中的边的最小值,并且不会走让图成环的路径. 这个算法的实现呢,首先我们需要两个集合, A:已被选择过的点. B: 未被选择过的点. 要做的就是在已被选择的点中找权值最小,且还没选择过的点的边. 这里需要利用优先级队列,将与A集合中顶点相连的边都加入优先级队列中. 在添加边关系前,只需要判断这条边的两个顶点是否都在A集合, 如果都在,相连后就会成环. 需要pop掉这条边

直接上代码:

W Prim(Self& mintree,const V& src)//普里姆算法,返回权重总和
{size_t srci = GetIndex(src);//找到最初点的下标int n = _vertex.size();mintree._vertex = _vertex;//最开始传入的最小生成树是没有初始化的,没有点和边的关系,若直接使用add会报错mintree._edge.resize(n);mintree._index = _index;for (int i = 0; i < n; i++)mintree._edge[i].resize(n, MAX_W);unordered_set<int> x;//已被选过的顶点集合unordered_set<int> y;//未被选过的顶点集合x.insert(srci);for (int i = 0; i < n; i++)if (i != srci)y.insert(i);//从X集合中找到权值最小,并且在Y集合中的边priority_queue<EDGE, vector<EDGE>, greater<EDGE>> minqueue;//利用优先级队列,将与X集合中相关联的边都放入优先级队列,在添加边时只需要判断这条边的两个顶点是否都在X集合,若都在X集合,添加后会形成环,不可取for (size_t i = 0; i < n; i++)if (_edge[srci][i] != MAX_W)minqueue.push(EDGE(srci, i, _edge[srci][i]));//先把srci连接的边添加到队列当中W totalvalue = W();int size = 0;while (!minqueue.empty()){while (!minqueue.empty() && x.find(minqueue.top()._srci) != x.end() && x.find(minqueue.top()._desti) != x.end())//把不符合要求的边给去掉minqueue.pop();EDGE min = minqueue.top();minqueue.pop();mintree._AddEdge(min._srci, min._desti, min._w);size++;x.insert(min._desti);y.erase(min._desti);totalvalue += min._w;if (size == n - 1) break;for (int i = 0; i < n; i++)if (x.find(i) == x.end() && _edge[min._desti][i] != MAX_W)minqueue.push(EDGE(min._desti, i, _edge[min._desti][i]));//将与dest连接的所有的边都添加到优先级队列}return totalvalue;
}

若有不懂,欢迎私信


8. 总结以及拓展

关于图的最小生成树的两个算法其实都是使用的贪心策略,用局部最优找到全局最优. 但是关于这两个算法的正确性的验证这里由于篇幅有限就不多讲解了


🔎 下期预告:最短路径问题 🔍

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

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

相关文章

Android Compose 一:基础控件

Flutter 与 Compose 组件辣么像&#xff0c;难道是同一个google团队整的&#xff1b;也未深究&#xff0c;只是猜测。 创建项目 需要使用新版本Android studio&#xff0c;忽略步骤… 项目目录 MainActivity说明 1 系统默认页面 Preview 修饰的方法&#xff0c;只用来供开发…

【Linux】-Linux用户和权限与权限的修改[3]

目录 一、认知root用户 1、root用户&#xff08;超级管理员&#xff09; 2、su和exit命令 3、sudo命令 二、用户、用户组管理 1、用户管理 2、getent 三、查看权限控制 1、认知权限信息 四、修改权限控制 - chmod 五、修改权限控制 - chown 一、认知root用户 1、root…

C#调用电脑摄像头拍照

1.打开VS2019&#xff0c;新建一个Form窗体&#xff0c;工具->NuGet包管理工具->管理解决方案的NuGet包&#xff0c;在浏览里搜索AForge.Controls、AForge.Video.DirectShow&#xff0c;安装AForge.Controls和AForge.Video.DirectShow 2.安装AForge组件完成后&#xff0c…

Spring底层入门(十一)

1、条件装配 在上一篇中&#xff0c;我们介绍了Spring&#xff0c;Spring MVC常见类的自动装配&#xff0c;在源码中可见许多以Conditional...开头的注解&#xff1a; Conditional 注解是Spring 框架提供的一种条件化装配的机制&#xff0c;它可以根据特定的条件来控制 Bean 的…

如何快速提取出一个文件里面全部指定类型的文件的全部路径

首先&#xff0c;需要用到的这个工具&#xff1a; 度娘网盘 提取码&#xff1a;qwu2 蓝奏云 提取码&#xff1a;2r1z 打开工具&#xff0c;切换到第五个模块&#xff0c;文件批量复制模块&#xff08;快捷键&#xff1a;Ctrl5&#xff09; 点击右边的“搜索添加”按钮&#…

[windows系统安装/重装系统][step-2]BIOS设置UEFI引导、磁盘分区GPT分区、安装系统[含完整操作拍照图片]

重装系统三部曲 [windows系统安装/重装系统][step-1]U盘启动盘制作&#xff0c;微软官方纯净系统镜像下载-CSDN博客 [windows系统安装/重装系统][step-2]BIOS设置UEFI引导、磁盘分区GPT分区、安装系统[含完整操作拍照图片]-CSDN博客 [windows系统安装/重装系统][step-3]装驱动…

开源免费的定时任务管理系统:Gocron

Gocron&#xff1a;精准调度未来&#xff0c;你的全能定时任务管理工具&#xff01;- 精选真开源&#xff0c;释放新价值。 概览 Gocron是github上一个开源免费的定时任务管理系统。它使用Go语言开发&#xff0c;是一个轻量级定时任务集中调度和管理系统&#xff0c;用于替代L…

使用Android数据恢复恢复已删除的文件[Windows]

智能手机或平板电脑等 Android 设备为用户提供了发送、接收、处理和存储各种数据的能力。它提供了传统手机无法实现的多功能性和简化功能。即便如此&#xff0c;您管理存储在安卓设备中的数据的方式完全取决于您。如果您的手机出现问题&#xff0c;例如系统崩溃或操作系统更新失…

Python-VBA函数之旅-sum函数

目录 一、sum函数的常见应用场景 二、sum函数使用注意事项 三、如何用好sum函数&#xff1f; 1、sum函数&#xff1a; 1-1、Python&#xff1a; 1-2、VBA&#xff1a; 2、推荐阅读&#xff1a; 个人主页&#xff1a; https://myelsa1024.blog.csdn.net/ 一、sum函数的常…

2024.5.19 机器学习周报

引言 Abstract 文献阅读 1、题目 X-HRNET: TOWARDS LIGHTWEIGHT HUMAN POSE ESTIMATION WITH SPATIALLY UNIDIMENSIONAL SELF-ATTENTION 2、引言 高分辨率表示是人体姿态估计实现高性能所必需的&#xff0c;随之而来的问题是高计算复杂度。特别地&#xff0c;主要的姿态估…

外网如何访问内网?快解析

由于公网IP资源短缺&#xff0c;我们的电脑大多处于内网环境&#xff0c;如何在外网访问内网电脑&#xff0c;成为一个令人头疼的问题&#xff0c;下面我给大家推荐一个非常实用的方法。 1&#xff1a;访问快解析下载安装快解析服务器 2&#xff1a;运行软件&#xff0c;点击“…

ASP.NET MVC(二) HtmlHelper

强类型 》》》 Form Html.Action() 执行一个Action&#xff0c;并返回html字符串。 Html.ActionLink() 生成一个超链接。 》》》 htmlhelper 扩展方法 /// 扩展方法 三要素 静态类静态方法this 》》》》上面需要引入命名空间&#xff0c; 》》》 不需要引入命名空间 pu…

systrace使用

systrace使用 chrome://tracing/ 抓trace方法 1.脚本 在sdk/platformtools/systrace文件目录下执行&#xff1a; python2 systrace.py -b 32000 -o setting_qian.html gfx input view webview wm am audio video camera app ss sched irq freq idle disk load sync workq reg…

Sublime Text for Mac:强大的文本编辑器

Sublime Text for Mac&#xff0c;一款轻量而强大的文本编辑器&#xff0c;为您的编程和写作工作带来无限可能。它以其简洁的界面和出色的性能&#xff0c;成为Mac用户中备受推崇的编辑器之一。 Sublime Text支持多种编程语言&#xff0c;无论是Python、JavaScript、HTML还是CS…

GNSS地表位移监测仪的工作原理

TH-WY1GNSS地表位移监测仪是一种用于实时监测地表位移变化的仪器设备。它主要利用全球导航卫星系统(GNSS)或全球定位系统(GPS)技术&#xff0c;通过接收卫星信号来测量地表点位的移动变化&#xff0c;从而获取地表点位的精确坐标信息&#xff0c;进而监测地表的水平和垂直位移情…

基于微信小程序的预约挂号系统(源码)

博主介绍&#xff1a;✌程序员徐师兄、10年大厂程序员经历。全网粉丝12W、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447…

中控系统智能化管理,多媒体展厅展示效果大升级!

在当今数字展厅设计的热潮中&#xff0c;多媒体互动理念已经崭露头角&#xff0c;成为各大企业竞相采纳的主流设计方式&#xff0c;它们通过集成的多媒体展示手段&#xff0c;为企业提供了一个全新的平台&#xff0c;来展现其形象、产品与服务&#xff0c;更通过互动的方式加深…

使用nvm安装node.js过程

今天Jade尝试安装nvm&#xff0c;并使用命令安装node.js但是碰到了一些问题&#xff0c;在此作为学习记录分享出来。希望可以留下深刻的印象&#xff1a; 1、概念了解 nvm----- (Node.js version manager)是一个命令行应用&#xff0c;可以协助您快速地 更新、安装、使用、卸载…

ChatGLM 本地部署指南(问题解决)

硬件要求&#xff08;模型推理&#xff09;&#xff1a; INT4 &#xff1a; RTX3090*1&#xff0c;显存24GB&#xff0c;内存32GB&#xff0c;系统盘200GB 如果你没有 GPU 硬件的话&#xff0c;也可以在 CPU 上进行推理&#xff0c;但是推理速度会更慢。 模型微调硬件要求更高。…

python数据分析——数据分类汇总与统计

数据分类汇总与统计 前言一、Groupby分类统计语法按列分组示例一示例二示例三 遍历各分组示例 使用字典和Series分组示例 使用函数分组示例 二、数据聚合groupby的聚合函数示例一示例二 逐列及多函数应用示例一示例二 返回不含行索引的聚合数据示例 三、一般性的“拆分-应用-合…