当前位置: 首页 > news >正文

【C++从0到王者】第四十六站:图的深度优先与广度优先

文章目录

  • 一、图的遍历
  • 二、广度优先遍历
    • 1.思想
    • 2.算法实现
    • 3.六度好友
  • 三、深度优先遍历
    • 1.思想
    • 2.代码实现
  • 四、其他问题

一、图的遍历

对于图而言,我们的遍历一般是遍历顶点,而不是边,因为边的遍历是比较简单的,就是邻接矩阵或者邻接表里面的内容。而对于遍历顶点就稍微有点麻烦了。

给定一个图G和其中任意一个顶点v0,从v0出发,沿着图中各边访问图中的所有顶点,且每个顶点仅被遍历一次。"遍历"即对结点进行某种操作的意思。

树以前前是怎么遍历的,此处可以直接用来遍历图吗?为什么?

树以前的遍历有深度优先(先序、中序、后序)和广度优先遍历(层序遍历)两种

图也是类似的。

二、广度优先遍历

1.思想

下面是广度优先遍历的一个比较形象的例子

image-20240219162241498

对于下面的图而言,也是类似的,先去找A,然后去遍历A的周围的三个结点,然后遍历这三个结点的周围结点,一层一层往外遍历,最终遍历完所有的结点,需要注意的是不要重复遍历了!

image-20240219162326247

2.算法实现

我们这里用邻接矩阵来实现我们的代码。如下代码所示。

namespace matrix
{//V代表顶点, W是weight代表权值,MAX_W代表权值的最大值,Direction代表是有向图还是无向图,flase表示无向template<class V, class W, W Max_W = INT_MAX, bool Direction = false>class Graph{public://图的创建//1. IO输入 不方便测试//2. 图结构关系写到文件,读取文件//3. 手动添加边Graph(const V* a, size_t n){_vertexs.reserve(n);for (size_t i = 0; i < n; i++){_vertexs.push_back(a[i]);_indexMap[a[i]] = i;}_matrix.resize(n);for (size_t i = 0; i < _matrix.size(); i++){_matrix[i].resize(n, Max_W);}}size_t GetVertexIndex(const V& v){//return _indexMap[v];auto it = _indexMap.find(v);if (it != _indexMap.end()){return it->second;}else{//assert(false)throw invalid_argument("顶点不存在");return -1;}}void AddEdge(const V& src, const V& dst, const W& w){size_t srci = GetVertexIndex(src);size_t dsti = GetVertexIndex(dst);_matrix[srci][dsti] = w;if (Direction == false){_matrix[dsti][srci] = w;}}void Print(){for (size_t i = 0; i < _vertexs.size(); i++){cout << "[" << i << "]" << "->" << _vertexs[i] << endl;}cout << endl;cout << "   ";for (int i = 0; i < _vertexs.size(); i++){//cout << _vertexs[i] << " ";printf("%-3d ", i);}cout << endl;for (size_t i = 0; i < _matrix.size(); i++){//cout << _vertexs[i] << " ";printf("%d ", i);for (size_t j = 0; j < _matrix[i].size(); j++){if (_matrix[i][j] == INT_MAX){cout << " * " << " ";}else{printf("%-3d ", _matrix[i][j]);//cout << _matrix[i][j] << " ";}}cout << endl;}}void BFS(const V& src){int srci = GetVertexIndex(src);queue<int> q; //广度遍历的队列vector<bool> visited(_vertexs.size(), false); //标记数组q.push(srci); //起点入队visited[srci] = true; //已经被遍历过了while (!q.empty()){int front = q.front();q.pop();cout << front << ":" << _vertexs[front] << endl;//把front顶点的邻接顶点入队列for (size_t i = 0; i < _matrix[front].size(); i++){if (_matrix[front][i] != Max_W){if (visited[i] == false){q.push(i);visited[i] = true;}}}}} private:vector<V> _vertexs; //顶点集合map<V, int> _indexMap; //顶点对应的下标关系vector<vector<W>> _matrix; //临界矩阵};

在上面的代码当中,这个图的如下所示

image-20240219171331165

在BFS的时候,我们使用一个队列和一个标记数组来解决。

我们先将第一个起点入队,并且进行标记已经被遍历了,然后像二叉树的层序遍历一样,一层一层去寻找它的周围结点。由于我们用的是邻接矩阵,那么我们就可以以出队列的这个结点为起点,遍历邻接矩阵的对应行,找到满足的进行入队列,然后依次进行标记。从而最终可以遍历整个图

最终结果为

image-20240219171940093

3.六度好友

如下面的题目就是一个简单的广度优先遍历

image-20240219162459971

这道题与二叉树中求出第几层的元素是十分类似的。就是层序遍历,不过要打印出第六层的结果

void BFSLevel(const V& src)
{int srci = GetVertexIndex(src);queue<int> q; //广度遍历的队列vector<bool> visited(_vertexs.size(), false); //标记数组q.push(srci); //起点入队visited[srci] = true; //已经被遍历过了int levelSize = 1;while (!q.empty()){for (int i = 0; i < levelSize; i++){int front = q.front();q.pop();cout << front << ":" << _vertexs[front] << " ";//把front顶点的邻接顶点入队列for (size_t i = 0; i < _matrix[front].size(); i++){if (_matrix[front][i] != Max_W){if (visited[i] == false){q.push(i);visited[i] = true;}}}}cout << endl;levelSize = q.size();}
}
void TestGraphBDFS()
{string a[] = { "张三", "李四", "王五", "赵六", "周七" };Graph<string, int> g1(a, sizeof(a) / sizeof(string));g1.AddEdge("张三", "李四", 100);g1.AddEdge("张三", "王五", 200);g1.AddEdge("王五", "赵六", 30);g1.AddEdge("王五", "周七", 30);g1.Print();g1.BFS("张三");cout << endl;g1.BFSLevel("张三");
}

这里我们用一个循环来记录每层的个数,每打印够一层就换行。如上代码所示

运行结果为

image-20240219174014273

三、深度优先遍历

1.思想

image-20240219180359909

如上是深度优先的一个形象的案例,下面是深度优先在一个图中的实际场景

image-20240219180429488

我们可以看到,他就像二叉树的先序遍历一样,一直走到最深层,然后退回去。这里需要注意的就是要进行标记已经遍历过的结点

2.代码实现

如下是深度优先的代码实现

namespace matrix
{//V代表顶点, W是weight代表权值,MAX_W代表权值的最大值,Direction代表是有向图还是无向图,flase表示无向template<class V, class W, W Max_W = INT_MAX, bool Direction = false>class Graph{public://图的创建//1. IO输入 不方便测试//2. 图结构关系写到文件,读取文件//3. 手动添加边Graph(const V* a, size_t n){_vertexs.reserve(n);for (size_t i = 0; i < n; i++){_vertexs.push_back(a[i]);_indexMap[a[i]] = i;}_matrix.resize(n);for (size_t i = 0; i < _matrix.size(); i++){_matrix[i].resize(n, Max_W);}}size_t GetVertexIndex(const V& v){//return _indexMap[v];auto it = _indexMap.find(v);if (it != _indexMap.end()){return it->second;}else{//assert(false)throw invalid_argument("顶点不存在");return -1;}}void AddEdge(const V& src, const V& dst, const W& w){size_t srci = GetVertexIndex(src);size_t dsti = GetVertexIndex(dst);_matrix[srci][dsti] = w;if (Direction == false){_matrix[dsti][srci] = w;}}void Print(){for (size_t i = 0; i < _vertexs.size(); i++){cout << "[" << i << "]" << "->" << _vertexs[i] << endl;}cout << endl;cout << "   ";for (int i = 0; i < _vertexs.size(); i++){//cout << _vertexs[i] << " ";printf("%-3d ", i);}cout << endl;for (size_t i = 0; i < _matrix.size(); i++){//cout << _vertexs[i] << " ";printf("%d ", i);for (size_t j = 0; j < _matrix[i].size(); j++){if (_matrix[i][j] == INT_MAX){cout << " * " << " ";}else{printf("%-3d ", _matrix[i][j]);//cout << _matrix[i][j] << " ";}}cout << endl;}}void BFS(const V& src){int srci = GetVertexIndex(src);queue<int> q; //广度遍历的队列vector<bool> visited(_vertexs.size(), false); //标记数组q.push(srci); //起点入队visited[srci] = true; //已经被遍历过了while (!q.empty()){int front = q.front();q.pop();cout << front << ":" << _vertexs[front] << endl;//把front顶点的邻接顶点入队列for (size_t i = 0; i < _matrix[front].size(); i++){if (_matrix[front][i] != Max_W){if (visited[i] == false){q.push(i);visited[i] = true;}}}}} void BFSLevel(const V& src){int srci = GetVertexIndex(src);queue<int> q; //广度遍历的队列vector<bool> visited(_vertexs.size(), false); //标记数组q.push(srci); //起点入队visited[srci] = true; //已经被遍历过了int levelSize = 1;while (!q.empty()){for (int i = 0; i < levelSize; i++){int front = q.front();q.pop();cout << front << ":" << _vertexs[front] << " ";//把front顶点的邻接顶点入队列for (size_t i = 0; i < _matrix[front].size(); i++){if (_matrix[front][i] != Max_W){if (visited[i] == false){q.push(i);visited[i] = true;}}}}cout << endl;levelSize = q.size();}}void _DFS(size_t srci, vector<bool>& visited){cout << srci << ":" << _vertexs[srci] << endl;visited[srci] = true;for (int i = 0; i < _matrix[srci].size(); i++){if (_matrix[srci][i] != Max_W && visited[i] == false){_DFS(i, visited);}}}void DFS(const V& src){int srci = GetVertexIndex(src);vector<bool> visited(_vertexs.size(), false);_DFS(srci, visited);}private:vector<V> _vertexs; //顶点集合map<V, int> _indexMap; //顶点对应的下标关系vector<vector<W>> _matrix; //临界矩阵};void TestGraph(){Graph<char, int, INT_MAX, true> g("0123", 4);g.AddEdge('0', '1', 1);g.AddEdge('0', '3', 4);g.AddEdge('1', '3', 2);g.AddEdge('1', '2', 9);g.AddEdge('2', '3', 8);g.AddEdge('2', '1', 5);g.AddEdge('2', '0', 3);g.AddEdge('3', '2', 6);g.Print();}void TestGraphBDFS(){string a[] = { "张三", "李四", "王五", "赵六", "周七" };Graph<string, int> g1(a, sizeof(a) / sizeof(string));g1.AddEdge("张三", "李四", 100);g1.AddEdge("张三", "王五", 200);g1.AddEdge("王五", "赵六", 30);g1.AddEdge("王五", "周七", 30);g1.Print();g1.BFS("张三");cout << endl;g1.BFSLevel("张三");cout << endl;g1.DFS("张三");}}

像先序遍历一样,这里也是需要一个子函数比较好的,因为我们需要使用递归,让子函数去进行递归是最好的

运行结果如下所示

image-20240219180714110

四、其他问题

关于深度优先和广度优先,上面的清空自然是很理想的情况。并且由于起点不同,深度优先和广度优先的结果是不同的。但是有时候,也会出现下面的问题。

比如图不连通的问题。也就是图存在孤立的结点。那么这样的话,以某个点为起点就没有遍历完成

这里我们可以有个解决方案是从visited数组中寻找没有遍历的结点,在进行一次深度优先或者广度优先。也就是要在原来的代码上在套一层。

相关文章:

【C++从0到王者】第四十六站:图的深度优先与广度优先

文章目录 一、图的遍历二、广度优先遍历1.思想2.算法实现3.六度好友 三、深度优先遍历1.思想2.代码实现 四、其他问题 一、图的遍历 对于图而言&#xff0c;我们的遍历一般是遍历顶点&#xff0c;而不是边&#xff0c;因为边的遍历是比较简单的&#xff0c;就是邻接矩阵或者邻接…...

Docker技术概论(2):Docker环境的搭建

Docker技术概论&#xff08;2&#xff09; Docker环境的搭建 - 文章信息 - Author: 李俊才 (jcLee95) Visit me at: https://jclee95.blog.csdn.netMy WebSite&#xff1a;http://thispage.tech/Email: 291148484163.com. Shenzhen ChinaAddress of this article:https://blo…...

电脑休眠之后唤不醒

现象&#xff1a;午休时间电脑休眠了&#xff0c;醒来之后发现在密码输入界面&#xff0c;但鼠标键盘没反应。按重启键或电源机重新开机&#xff0c;结果开不了机。 原因&#xff1a;1、内存条脏了&#xff0c;导致内存条读取失败 2、休眠的时候硬盘休眠了&#xff0c;导致按…...

Python列表中添加删除元素不走弯路

1.append() 向列表中添加单个元素&#xff0c;一般用于尾部追加 list1 ["香妃", "乾隆", "贾南风", "赵飞燕", "汉武帝"]list1.append("周瑜") print(list1) # [香妃, 乾隆, 贾南风, 赵飞燕, 汉武帝, 周瑜]…...

MATLAB环境下脑电信号EEG的谱分析

脑电信号一直伴随着人类的生命&#xff0c;脑电波是脑神经细胞发生新陈代谢、离子交换时细胞群兴奋突触电位总和&#xff0c;脑电信号的节律性则和丘脑相关&#xff0c;含有丰富的大脑活动信息。通常我们所接触的脑电图都是头皮脑电图&#xff0c;在有些特殊场合还需要皮下部位…...

librtmp源码分析

阅读了librtmp的源码&#xff0c;简单记录下。 首先补充下AMF格式基本知识 1 AMF格式 AMF是Action Message Format(动作消息格式)的简写&#xff0c;它是一种二进制的数据格式。它的设计是为了把actionscript里面的数据(包括Object, Array, Boolean, Number等)序列化成二进制…...

CCDP.00.问老师问题前你首先需要做的事情

一、一定要按老师要求做好快照&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 1、在关键节点处&#xff0c;比如做完Part1后&#xff0c;关机状态下做快照。 2、在做没把握的操作前先做快照&#xff08;这个可以在开机状态下做快照&#xff0c;但推荐关机状态…...

「算法」常见位运算总结

位运算符 异或 按位异或可以实现无进位相加&#xff0c;所谓无进位相加&#xff0c;就是在不考虑进位的情况下将两个数相加&#xff08;后面有道题需要用到这种操作&#xff09; 异或的运算律 ①a ^ 0 a ②a ^ a 0 ③a ^ b ^ c a ^ ( b ^ c ) 有符号右移>> 将一个…...

【C++初识】语句

文章目录 1.注释 变量 常量 关键字 标识符命名规则 数据类型 sizeof关键字 数据的输入 运算符2.程序流程结构2.1选择结构2.2循环结构2.21while{循环条件}{循环语句}&#xff1b;//满足循环条件&#xff0c;执行循环语句2.22do{循环语句}while{循环条件}&#xff1b;//do....whi…...

Python线性代数傅里叶分析和动态系统模拟分析之一

要点 Python向量数值计算、可视化&#xff0c;线性独立性和子空间。了解欧几里德距离、余弦相似度和皮尔逊相关性应用案例&#xff1a;Python数值计算文档相似度时间序列和特征检测示例&#xff1a;Python信号处理边缘检测器, K均值示例&#xff1a;随机簇质心分布Python傅里叶…...

mysql插入GEOMETRY相关字段类型(point,linestring等)

一、问题 向mysql中插入point&#xff0c;linestring等相关空间坐标字段&#xff0c;出现报错&#xff1a; 1416 - Cannot get geometry object from data you send to the GEOMETRY field要插入的数据&#xff1a;...

vue3学习 【5】watch的使用

什么是watch 当我们需要根据一个数据的变化来进行一些操作的时候我们需要使用侦听器&#xff0c;它能够在响应式数据发生变化的时候触发提供的回调函数 基础侦听 watch 可以侦听不同的数据源。例如&#xff1a; ref计算属性响应式对象getter函数多个数据源组层的数据 cons…...

PyTorch深度学习快速入门

PyTorch深度学习快速入门 1.PyTorch环境配置及安装2.python编辑器的选择、安装、配置&#xff08;pycharm、JupyTer安装&#xff09;3.为什么torch.cuda.is_available()返回false4.python学习中两大法宝函数&#xff08;也可用在pytorch&#xff09;5.pycharm和jupyter&#xf…...

种花

分情况&#xff1a; 第一盆k种选择&#xff0c;之后全部k-1种选择 每次相乘结果对1e97取模 #include <iostream> #include <vector> #include <algorithm> using namespace std; #define endl \n const int N 1e9 7;int main() {ios::sync_with_stdio(f…...

Android Shadow插件化框架分析与集成(二)

本文索引 前言插件打包后如何交给宿主使用?宿主加载插件代码分析全局初始化操作加载插件activity测试过程中遇到的问题报错 1 :报错2:报错3 :二次开发支持多插件、多进程功能mPpsController 的构造方式mPluginLoader的构造方式多插件如何改造前言...

Go 与 Rust:导航编程语言景观

在当今构建软件时&#xff0c;开发者在编程语言上有着丰富的选择。两种脱颖而出的语言是 Go 和 Rust - 都很强大但却截然不同。本文将从各种因素比较这两种语言&#xff0c;以帮助您确定哪种更适合您的需求。 我们将权衡它们在并发、安全性、速度、互操作性等方面的方法。我们将…...

包管理工具之npm也慌了?

起因 因为npm的种种问题,我很早就换成了pnpm和yarn(但是其实npm也在使用),已经很久没有关注npm的功能更新了。最近无意间进入Node18版本的安装目录,发现其除了常规的node,npm等默认安装了一个新的包corepack,这个就是今天我要分享的东西了。 注: 我因为18版本的node上…...

mobile app 安全扫描工具MobSF了解下

可以干啥&#xff1a; static 静态分析 dynamic 动态分析 可以用来渗透了 如何docker安装 docker image 下载地址https://hub.docker.com/r/opensecurity/mobile-security-framework-mobsf/ setup 两行即可 1 docker pull opensecurity/mobile-security-framework-mobsf…...

Gophish+EwoMail 自建钓鱼服务器

GophishEwoMail 自建钓鱼服务器 文章目录 GophishEwoMail 自建钓鱼服务器1.前提准备2.搭建EwoMail邮件服务器1&#xff09;Centos7 防火墙操作2&#xff09;设置主机名3&#xff09;host配置4&#xff09;安装EwoMail5&#xff09;获取DKIM6&#xff09;端口服务介绍7&#xff…...

Dockerfile(5) - CMD 指令详解

CMD 指定容器默认执行的命令 # exec 形式&#xff0c;推荐 CMD ["executable","param1","param2"] CMD ["可执行命令", "参数1", "参数2"...]# 作为ENTRYPOINT的默认参数 CMD ["param1","param…...

亚马逊人的mbti来了?测出结果真令人哭笑不得!

做亚马逊久了&#xff0c;总好奇身边同行都是什么 “路子”—— 有人是数据控&#xff0c;算利润算到小数点后两位&#xff1b; 有人凭直觉选品&#xff0c;偏偏总能踩中蓝海&#xff1b; 有人社牛到站外红人随便聊&#xff0c;也有人只想安静守链接不被打扰。 抱着好玩、图一…...

计算机网络差错控制技术全解析:从奇偶校验到CRC的实战应用

1. 为什么我们需要差错控制技术&#xff1f; 想象一下你正在给朋友发送一条重要消息&#xff1a;"明天下午3点会议室见"。如果传输过程中某个比特位发生了翻转&#xff0c;比如"3"变成了"1"&#xff0c;结果变成了"明天下午1点会议室见&quo…...

伸缩数据线充电宝:倍思灵动充让年轻人的出行,不再有“线”制

当代年轻人对充电宝的期待,早已超越“能充电”本身。在快节奏的移动场景中,他们追求的是“不打结、不缠绕、不占地方”的简洁体验。而伸缩数据线充电宝的出现,恰好击中了这一需求痛点。倍思推出的灵动充伸缩线充电宝,则是这一趋势下的典型代表。它凭借“轻量化出行”和“可靠耐…...

收藏!大模型求职避坑指南:别再死背八股,这样准备才稳过面试(小白/程序员必看)

最近和不少研一、研二的同学&#xff0c;还有刚入门大模型的程序员聊天&#xff0c;发现大家都在踩同一个坑&#xff1a;刷了上百道八股题&#xff0c;Transformer的结构、注意力机制倒背如流&#xff0c;RAG的每个模块&#xff08;检索、召回、重排&#xff09;都能侃侃而谈&a…...

Qwen3-0.6B-FP8惊艳效果:同一问题不同Temperature下的创造性梯度展示

Qwen3-0.6B-FP8惊艳效果&#xff1a;同一问题不同Temperature下的创造性梯度展示 你有没有想过&#xff0c;同一个问题问AI&#xff0c;为什么每次的回答都不一样&#xff1f;有时候它回答得严谨认真&#xff0c;有时候又天马行空充满创意&#xff1f; 这背后其实有个关键的“…...

BEYOND REALITY Z-Image参数调优:步数、CFG Scale这样设,人像更自然

BEYOND REALITY Z-Image参数调优&#xff1a;步数、CFG Scale这样设&#xff0c;人像更自然 1. 理解关键参数对人像生成的影响 BEYOND REALITY Z-Image作为一款专注于写实人像生成的AI工具&#xff0c;其生成效果很大程度上取决于两个核心参数的设置&#xff1a;步数(Steps)和…...

Wan2.2-I2V-A14B项目实战:从零搭建个人AI艺术画廊网站

Wan2.2-I2V-A14B项目实战&#xff1a;从零搭建个人AI艺术画廊网站 1. 项目概述与价值 想象一下&#xff0c;你可以在自己的网站上展示由AI生成的独特艺术作品&#xff0c;让访客欣赏、点赞甚至参与创作。这正是我们将要实现的个人AI艺术画廊网站。这个项目不仅能让你的创意作…...

AI元人文:意义行为原生论的发生学阐明与伦理中间件建构

AI元人文&#xff1a;意义行为原生论的发生学阐明与伦理中间件建构摘要&#xff1a;本文旨在系统阐述一种名为“意义行为原生论”的理论框架&#xff0c;其核心结构为“舍得结构”。该理论拒斥将意义视为某种先验实体或行为结果的附属品&#xff0c;而是将其锚定于D&#xff08…...

RVC开源贡献指南:如何为RVC WebUI新增语言/功能模块

RVC开源贡献指南&#xff1a;如何为RVC WebUI新增语言/功能模块 1. 引言&#xff1a;从使用者到贡献者 你可能已经用RVC WebUI玩过AI翻唱&#xff0c;或者用它把自己的声音变成各种有趣的音色。这个工具确实强大&#xff0c;3分钟就能训练一个新模型&#xff0c;让语音转换变…...

Tag-it 事件处理完全手册:从点击到移除的全流程控制

Tag-it 事件处理完全手册&#xff1a;从点击到移除的全流程控制 【免费下载链接】tag-it aehlke/tag-it: 是一个用于管理文件标签的 jQuery 插件。适合对 jQuery、HTML 和想要管理文件标签的开发者。 项目地址: https://gitcode.com/gh_mirrors/ta/tag-it Tag-it 是一款…...