[高阶数据结构五] 图的遍历和最小生成树

1.前言

本篇文章是在认识图的基础上进行叙述的,如果你还不知道什么是图,图的存储结构。那么请你先阅读以下文章。

[高阶数据结构四] 初始图论-CSDN博客

本章重点:

本篇主要讲解及模拟实现图的遍历算法--DFS和BFS,以及最小生成树算法Kruskal和Prim算法。

2.什么是图的遍历

图的遍历就是,从任意一个顶点开始,可以通过某种方式走过所有的顶点,这就叫做图的遍历。

那么图可以用什么方法来进行遍历呢?之前二叉树的遍历可以通过递归的办法和层序遍历的方法来获取结果,请思考那么图是否也可以使用这种方法呢?---答案是可以的。这就是DFS和BFS

2.1 图的广度优先遍历

先解释什么叫做广度优先遍历:简单来说就是先把一个顶点的相邻朋友顶点全部走完。

例子如下:

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

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

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

转换到图上面那就是如下情形

模拟一遍过程

假设以A顶点为源点开始遍历,对于顶点A来说, 先走B,C,D. 然后再看B顶点, 走A,C,E, 但是A和C已经走过了,所以不能再此将它们算进去. --那么用什么方法来记录他们是否走过呢?做法很简单, 使用一个visited数组, 数组用来存储一个顶点是否已经入过队列了. 

那用什么容器来临时存储这些值呢?--很明显用队列,因为你把A放进容器之后,要判断A的朋友是哪些,然后把A拿出容器,这就联想到了先进先出的情形,所以采用队列这个容器

细节把握清楚了,那么写出代码来就不是很大的问题了。直接上代码

		void BFS(const V& src)//广度优先遍历{int srci = GetIndex(src);if (srci == -1) return;vector<bool> visited(_ver.size(), false);queue<int> que;que.push(srci);visited[srci] = true;cout << _ver[srci] << " ";while (!que.empty()){int front = que.front();que.pop();for (int i = 0; i < _ver.size(); ++i){if (_martix[front][i] != int_MAX && visited[i] == false){que.push(i);visited[i] = true;cout << _ver[i] << " ";}}}cout << endl;}

注意:这个代码要和前面的初始图论里面的代码结合起来看,这是在邻接矩阵里面实现的。

2.2 图的深度优先遍历

图的优先遍历就是一条路走到黑,走不通再回退

例子如下:

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

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

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

模拟一遍过程:

这之前和树的递归和回溯类似的,代码写起来也很简单

代码如下:

	void _DFS(int index, vector<bool>& visited){if (!visited[index]){cout << _ver[index] << " ";visited[index] = true;//遍历与这个点所有相邻的点for (int i = 0; i < _ver.size(); ++i){if (_martix[index][i] != int_MAX && visited[i] == false)_DFS(i, visited);}}}void DFS(const V& v)//深度优先遍历{int srci = GetIndex(v);if (srci == -1) return;vector<bool> visited(_ver.size(), false);_DFS(srci, visited);//有可能这个图并不是连通的,所以最好都遍历一遍for (int i = 0; i < _ver.size(); ++i){if (visited[i] == false)_DFS(i, visited);}}

3.图的最小生成树

在讲解最小生成树之前先明确概念。

连通图中的每一棵生成树,都是原图的一个极大无环子图,即: 从其中删去任何一条边,生成树
就不在连通;反之,在其中引入任何一条新边,都会形成一条回路

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

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

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

最小生成树也是图的子图,但是又满足如下的规则:

  1. 首先要是一个连通图:那么就表示任意两点间都有路径能去
  2. 其次就是不能有环
  3. 子图中要包含所有的顶点,且边刚好是n-1条
  4. 子图中所有边的权值要是最小

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

3.1 Kruskal算法

给一个有 n 个顶点的连通网络 N={V,E} 首先构造一个由这 n 个顶点组成、不含任何边的图 G={V,NULL} ,其中每个顶点自成一个连通分量, 其次不断从 E 中取出权值最小的一条边 ( 若有多条任取其一 ) ,若该边的两个顶点来自不同的连通分 量,则将此边加入到 G 。如此重复,直到所有顶点在同一个连通分量上为止。
简单来说就是给一个源点,然后先找出与源点相邻的节点,然后源点与这些相邻节点构成的边里面找出最小值,然后在从与选出节点的的相邻的边里面找出最小值,依次迭代。
核心思路: 每次迭代时,选出一条具有最小权值,且两端点不在同一连通分量上的边,加入生成树。
外界是要传一棵没有进行初始化的树,以及一个源点,然后从这个源点开始来进行选择。
这个算法的核心就是每次迭代时,找出与其相邻的最小边,那么怎么找最小呢?---那肯定是用优先级队列了,那么还有一个问题就是在你一直选边的过程中,你怎么知道是否构成了环呢?---那么就可以用之前学到过的并查集的思路,判断两个节点是否在一个里面,如果在那么就肯定是环,这条边就不能要,否则就可以要了。
代码如下:
typedef Graph<V, W,int_MAX, Direction> Self;struct EDGE{int _srci;int _dsti;W _w;EDGE(int& srci, int& dsti, W& w) :_srci(srci), _dsti(dsti), _w(w){}bool operator>(const EDGE& e) const{return _w > e._w;}};W Kruskal(Self& MinTree){// 最小生成树是每次都找出图中权值最小的边来构造最小生成树// 因此使用优先级队列来存储边以及边的两个点,根据权值来进行排序,小的优先级高// 并查集来判断是否形成了环--若两个点的公共祖先是同一个,那么肯定形成了环//由于传过来的最小生成树是未初始化的,所以要先进行赋值。否则后续使用会出问题MinTree._ver = _ver;MinTree._umapIndex = _umapIndex;MinTree._martix.resize(_ver.size());for (auto& e : MinTree._martix){e.resize(_ver.size(), int_MAX);}priority_queue<EDGE, vector<EDGE>, greater<EDGE>> que;size_t n = _ver.size();for (int i = 0; i < n; i++){for (int j = 0; j < n; j++){//在无向图中是沿对角线对称,所以只遍历一半即可if (i<j&&_martix[i][j] != int_MAX)que.push(EDGE(i, j, _martix[i][j]));}}//优先级队列和并查集都准备好了,现在开始往最小树里面添加边了W totalweight =W();int size = 0;UnionFindSet UFS(n);while (!que.empty()){EDGE min = que.top();que.pop();if (!UFS.SameSet(min._srci, min._dsti)){//不在一个集合cout << _ver[min._srci] << "->" << _ver[min._dsti] << ":" << min._w << endl;MinTree._AddEdge(min._srci, min._dsti, min._w);UFS.Union(min._srci, min._dsti);totalweight += min._w;size++;//边数+1}}if (size == n - 1) return totalweight;else return W();}

上诉代码用的是Greater这个仿函数,以及前面学到的并查集,如果不知道并查集的朋友建议阅读下面这篇文章。

[高阶数据结构(一)]并查集详解-CSDN博客

这里是传入一个空的图,这还是第一次遇见这种写法,以前都是把空间开好,以及初始化好,但是这种写法是非常简洁且相对来说简单的。若还有不懂的欢迎后台TT。

3.2 Prim算法

Prim算法的思想就是从源点开始,每次选择的是找出最小的边。但是与KrusKal算法的区别就是他是从未确定的点里面找出和我当前确定的这个点的最小边。然后依次从未确定的点里面找出所有的边为止。
例如:
在(a)里面,那么确定的点就是a,不确定的就是b,c,d,e,f,g,i,h。所以从这些点里面,找出与a直接相连构成最小边的点,找出了B。然后确定的点是a,b,所以不确定的点就是c,d,e,f,g,h,i。然后依次类推。这里面也需要判断是否构成环,那么是否构成换还需要像KrusKal算法里面那样,用并查集来判断吗?-----其实是不需要的,因为你这里有两个集合,一个是确定的点,一个是未确定的点,如果选出的两个点都在确定的点里面的话,那么肯定是构成环了,所以肯定是不成立的。那么就需要把这条边pop掉,方便选出后续小的边。
代码如下:
W Prim(Self& MinTree, const V& v)//传过来最小生成树,和开始的起点{//初始化最小生成树size_t n = _ver.size();MinTree._ver = _ver;MinTree._umapIndex = _umapIndex;MinTree._martix.resize(n);for (int i = 0; i < n; i++)MinTree._martix[i].resize(n, int_MAX);//第二步开始存储于起点连接的权值最小的边int srci = GetIndex(v);priority_queue<EDGE, vector<EDGE>, greater<EDGE>> pque;//与srci相连的节点在哪呢?--遍历srci这一行即可for (int i = 0; i < n; i++){if (_martix[srci][i] != int_MAX)pque.push(EDGE(srci, i, _martix[srci][i]));}//第三步,找两个集合,一个集合是确定是最小生成树的点,一个集和是还没有确定vector<bool> X(n, false);vector<bool> Y(n, false);X[srci] = true;//true表示在这个集合for (int i = 0; i < n; i++)if (srci != i)Y[i] = true;W totalweight = W();int size = 0;//第四步,开始找边while (!pque.empty()){EDGE min = pque.top();pque.pop();//一条边的起点一定在X里面,只要终点不在的话那就不会构成环了if (X[min._dsti]){cout << "选这条边的话,成环了" << endl;cout << "[" << _ver[min._srci] << "]" << "->" << "[" << _ver[min._dsti] \<< "]" << ": " << min._w << endl;}else{//找到了合适的边X[min._dsti] = true;Y[min._dsti] = false;MinTree._AddEdge(min._srci, min._dsti, min._w);cout << "[" << _ver[min._srci] << "]" << "->" << "[" << _ver[min._dsti] \<< "]" << ": " << min._w << endl;size++;totalweight += min._w;if (size == n - 1) break;//把所有的与min._dsti相连的边放入最小队列for (int i = 0; i < n; i++){if (X[i] != true && _martix[min._dsti][i] != int_MAX)pque.push(EDGE(min._dsti, i, _martix[min._dsti][i]));}}}if (size == n - 1) return totalweight;else return W();}

这个算法和前面的kruskal算法都是用的是贪心的策略,但是唯一不同的就是两者判断环的思路是不一致的,一个借助的是并查集,一个借助的是两个数组。

4.总结

到这里图的遍历和图的最小生成树算法就学完了,如果有不懂的伙伴,欢迎后台TT我哦。

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

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

相关文章

快速排序hoare版本和挖坑法(代码注释版)

hoare版本 #define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h>// 交换函数 void Swap(int* p1, int* p2) {int tmp *p1;*p1 *p2;*p2 tmp; }// 打印数组 void _printf(int* a, int n) {for (int i 0; i < n; i) {printf("%d ", a[i]);}printf("…

ROS VSCode调试方法

VSCode 调试 Ros文档 1.编译参数设置 cd catkin_ws catkin_make -DCMAKE_BUILD_TYPEDebug2.vscode 调试插件安装 可在扩展中安装(Ctrl Shift X): 1.ROS 2.C/C 3.C Intelliense 4.Msg Language Support 5.Txt Syntax 3.导入已有或者新建ROS工作空间 3.1 导入工作…

Java 基础面试 题(Java Basic Interview Questions)

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 本人主要分享计算机核心技…

【排序算法】之快速排序篇

思想&#xff1a; 分而治之&#xff0c;通过选定某一个元素作为基准值&#xff0c;将序列分为两部分&#xff0c;左边的序列小于基准值&#xff0c;右边的序列大于基准值&#xff0c; 然后再分别将左序列和右序列进行递归排序&#xff0c;直至每部分有序。 性质&#xff1a;这…

【数据结构】ArrayList与顺序表

ArrayList与顺序表 1.线性表2.顺序表2.1 接口的实现 3. ArrayList简介4. ArrayList使用4.2 ArrayList常见操作4.3 ArrayList的遍历4.4 ArrayList的扩容机制 5. ArrayList的具体使用5.1 杨辉三角5.2 简单的洗牌算法 6. ArrayList的问题及思考 【本节目标】 线性表顺序表ArrayLis…

基于RNNs+Attention的红点位置检测(pytorch)

文章目录 1 项目背景2 数据集3 思路3.1 一些小实验Ⅰ 多通道输入Ⅱ 位置编码Ⅲ 实验结果 3.2 AttentionⅠ Additive AttentionⅡ Dot-Product AttentionⅢ Scaled Dot-Product AttentionⅣ Multi-Head Attention (self-attention)Ⅴ Criss-Cross AttentionⅥ Result 4 代码 1 项…

RTSP摄像头、播放器为什么需要支持H.265?

H.264还是H.265&#xff1f; 好多开发者在做选RTSP播放器的时候&#xff0c;经常问我们的问题是&#xff0c;用H.264好还是H.265好&#xff1f;本文我们就H.264 和 H.265的主要区别和适用场景&#xff0c;做个大概的交流。 一、压缩效率 H.265 更高的压缩比 H.265 在相同视频…

Qt桌面应用开发 第八天(读写文件 文件编码 文件流)

目录 1.读文件 2.写文件及编码格式 2.1写文件 2.2编码格式 3.文件信息读取 4.文件流 4.1QTextStream 4.2QDataStream 1.读文件 需求&#xff1a;一个pushButton&#xff0c;点击之后可以选择一个txt文件的路径&#xff0c;路径会显示在lineEdit上&#xff0c;txt文件的…

chrome允许http网站打开摄像头和麦克风

第一步 chrome://flags/#unsafely-treat-insecure-origin-as-secure 第二步 填入网址&#xff0c;点击启用 第三步 重启 Chrome&#xff1a;设置完成后&#xff0c;点击页面底部的 “Relaunch” 按钮&#xff0c;重新启动 Chrome 浏览器&#xff0c;使更改生效。

IntelliJ IDEA(2024版) 的安装、配置与使用教程:常用配置、创建工程等操作(很详细,你想要的都在这里)

IDEA的安装、配置与使用&#xff1a; Ⅰ、IDEA 的安装&#xff1a;1、IDEA 的下载地址(官网)&#xff1a;2、IDEA 分为两个版本&#xff1a;旗舰版 (Ultimate) 和 社区版 (Community)其一、两个不同版本的安装文件&#xff1a;其二、两个不同版本的详细对比&#xff1a; 3、IDE…

AI前景分析展望——GPTo1 SoraAI

引言 人工智能&#xff08;AI&#xff09;领域的飞速发展已不仅仅局限于学术研究&#xff0c;它已渗透到各个行业&#xff0c;影响着从生产制造到创意产业的方方面面。在这场技术革新的浪潮中&#xff0c;一些领先的AI模型&#xff0c;像Sora和OpenAI的O1&#xff0c;凭借其强大…

基于SpringBoot实现的民宿管理系统(代码+论文)

&#x1f389;博主介绍&#xff1a;Java领域优质创作者&#xff0c;阿里云博客专家&#xff0c;计算机毕设实战导师。专注Java项目实战、毕设定制/协助 &#x1f4e2;主要服务内容&#xff1a;选题定题、开题报告、任务书、程序开发、项目定制、论文辅导 &#x1f496;精彩专栏…

ComfyUI | ComfyUI桌面版发布,支持winmac多平台体验,汉化共享等技巧!(内附安装包)

ComfyUI 桌面版正式推出&#xff0c;支持 Windows 与 macOS 等多平台&#xff0c;为 AI 绘画爱好者带来全新体验。其安装包便捷易用&#xff0c;开启了轻松上手之旅。汉化共享功能更是一大亮点&#xff0c;打破语言障碍&#xff0c;促进知识交流与传播。在操作上&#xff0c;它…

基于 Vue2.0 + Nest.js 全栈开发的后台应用

☘️ 项目简介 Vue2 Admin 是一个前端基于 Ant Design Pro 二次开发&#xff0c;后端基于 Nest.js 的全栈后台应用&#xff0c;适合学习全栈开发的同学参考学习。 &#x1f341; 前端技术栈&#xff1a; Vue2、Ant Design Vue、Vuex &#x1f341; 后端技术栈&#xff1a; Ne…

RabbitMQ 应用问题

文章目录 1. 幂等性保障什么是幂等性MQ 的幂等性如何处理消息重复的问题1. 全局唯一ID2. 业务逻辑判断 2. 顺序性保障什么是顺序性保障什么情况会打破RabbitMQ的顺序性顺序性保障方案 3. 消息积压什么是消息积压造成消息积压的原因解决消息积压的方案 结论 1. 幂等性保障 什么…

【数据库系列】MySQL基础知识:深入理解DDL、DML与DQL操作

MySQL是一个开源的关系型数据库管理系统&#xff08;RDBMS&#xff09;&#xff0c;广泛用于数据存储和管理。理解MySQL的基本操作至关重要&#xff0c;尤其是数据定义语言&#xff08;DDL&#xff09;、数据操作语言&#xff08;DML&#xff09;和数据查询语言&#xff08;DQL…

PAT1085 Perfect Sequence(25)

//判断是否是连续的数 //判断是否只能第一个数是最小值 #include <cstdio> #include <algorithm> typedef long long ll; using namespace std; int n,p; const int maxn 100010; int arr[maxn];int binary(int l, int r, ll tgt){if(arr[n-1] < tgt) return n…

Shell 编程基础知识

为什么要学 Shell&#xff1f; 学一个东西&#xff0c;我们大部分情况都是往实用性方向着想。从工作角度来讲&#xff0c;学习 Shell 是为了提高我们自己工作效率&#xff0c;提高产出&#xff0c;让我们在更少的时间完成更多的事情。 很多人会说 Shell 编程属于运维方面的知…

深入浅出UART驱动开发与调试:从基础调试到虚拟驱动实现

往期内容 本专栏往期内容&#xff1a;Uart子系统 UART串口硬件介绍深入理解TTY体系&#xff1a;设备节点与驱动程序框架详解Linux串口应用编程&#xff1a;从UART到GPS模块及字符设备驱动 解UART 子系统&#xff1a;Linux Kernel 4.9.88 中的核心结构体与设计详解IMX 平台UART驱…

Linux网络——IO模型和多路转接

通常所谓的IO&#xff0c;其本质就是等待通信和进行通信&#xff0c;即IO 等 拷贝。 那么想要做到高效的IO&#xff0c;就要在单位时间内&#xff0c;减少“等”的比重。 一.五种IO模型 阻塞 IO: 在内核将数据准备好之前, 系统调用会一直等待. 所有的套接字, 默认都是阻塞方…