【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数组中寻找没有遍历的结点,在进行一次深度优先或者广度优先。也就是要在原来的代码上在套一层。

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

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

相关文章

《汇编语言》第3版 (王爽)检测点3.1解析

第三章 检测点3.1 &#xff08;1&#xff09;.在Debug中&#xff0c;用“d 0:0 1f”查看内存&#xff0c;结果如下。 下面的程序执行前&#xff0c;AX 0&#xff0c;BX 0&#xff0c;写出每条汇编指令执行完后相关寄存器中的值。 mov ax,1 ;将1放入AX寄存器中&#xff0c;…

【零基础SRC】成为漏洞赏金猎人的第一课:加入玲珑安全漏洞挖掘班。

我们是谁 你是否对漏洞挖掘充满好奇&#xff1f;零基础或有基础但想更进一步&#xff1f;想赚取可观的漏洞赏金让自己有更大的自由度&#xff1f; 那么&#xff0c;不妨了解下我们《玲珑安全团队》。 玲珑安全团队&#xff0c;拥有多名实力讲师&#xff0c;均就职于互联网头…

一线互联网大厂中高级Android面试真题收录,记一次字节跳动Android社招面试

在开始回答前&#xff0c;先简单概括性地说说Linux现有的所有进程间IPC方式&#xff1a; 1. **管道&#xff1a;**在创建时分配一个page大小的内存&#xff0c;缓存区大小比较有限&#xff1b; 2. 消息队列&#xff1a;信息复制两次&#xff0c;额外的CPU消耗&#xff1b;不合…

指针与malloc动态内存申请,堆和栈的差异

定义了两个函数print_stack()和print_malloc()&#xff0c;分别演示了两种不同的内存分配方式&#xff1a;栈内存和堆内存。然后在main()函数中调用这两个函数&#xff0c;并将它们返回的指针打印出来。 由于print_stack()中的数组c是在栈上分配的&#xff0c;当函数返回后&…

Python装饰器的使用详解

目录 1、函数装饰器 1.1、闭包函数 1.2、装饰器语法 1.3、装饰带参数的函数 1.4、被装饰函数的身份问题 1.4.1、解决被装饰函数的身份问题 1.5、装饰器本身携带/传参数 1.6、嵌套多个装饰器 2、类装饰器 装饰器顾名思义作为一个装饰的作用&#xff0c;本身不改变被装…

Acwing 周赛135 解题报告 | 珂学家 | 反悔堆贪心

前言 整体评价 VP了这场比赛&#xff0c; T3挺有意思的&#xff0c;反悔贪心其实蛮套路的。 A. 买苹果 思路: 签到 n, x list(map(int, input().split())) print (n // x)B. 牛群 思路: 分类讨论 from collections import Counters input() cnt Counter(s)lists sorte…

WPF 【十月的寒流】学习笔记(2):MVVM中是怎么实现通知的

文章目录 前言相关链接代码仓库项目配置代码初始代码ViewPersonViewModel 尝试老办法通知解决方案ObservableCollectionBindingListICollectionView 总结 前言 我们这次详细了解一下列表通知的底层是怎么实现的 相关链接 十月的寒流 MVVM实战技巧之&#xff1a;可被观测的集合…

2024年【A特种设备相关管理(电梯)】考试总结及A特种设备相关管理(电梯)证考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年A特种设备相关管理&#xff08;电梯&#xff09;考试总结为正在备考A特种设备相关管理&#xff08;电梯&#xff09;操作证的学员准备的理论考试专题&#xff0c;每个月更新的A特种设备相关管理&#xff08;电梯…

C++:非静态成员默认初始化

C11之前只有常静态成员变量才能进行默认初始化&#xff0c;其它变量初始化时总要进行繁琐的过程 class A{int a; public:A():a(10){} };C11开始支持非静态成员的默认初始化&#xff0c;默认初始化和初始化参数列表同时初始化一个变量时会使用初始化参数列表&#xff0c;不进行…

JavaScript new、apply call 方法

new、apply、call、bind JavaScript 中的 apply、call和 bind 方法是前端代码开发中相当重要的概念&#xff0c;并且与 this 的指向密切相关 new new 关键词的主要作用 就是执行一个构造函数、返回一个实例对象 根据构造函数的情况&#xff0c;来确定是否可以接受参数的传递…

Huggingface初上手即ERNIE-gram句子相似性实战

大模型如火如荼的今天&#xff0c;不学点语言模型&#xff08;LM&#xff09;相关的技术实在是说不过去了。只不过由于过往项目用到LM较少&#xff0c;所以学习也主要停留在直面——动眼不动手的水平。Huggingface&#xff08;HF&#xff09;也是现在搞LM离不开的工具了。 出于…

最新 DataGrip 2023.3.4 下载安装激活 + 永久免费

文章目录 DataGrip简介同类产品对比使用技巧不足实战 下载安装激活Stage 1 : 官网下载Stage 2 : 下载工具Stage 3-1 : windows为例Stage 3-2 : mac为例常见问题部分小伙伴 Mac 系统执行脚本遇到如下错误&#xff1a;解决方法&#xff1a; 执行脚本做了啥&#xff1f;和收费版区…

基于springboot+vue的可盈保险合同管理系统

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战&#xff0c;欢迎高校老师\讲师\同行交流合作 ​主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Pyt…

XUbuntu22.04之如何找到.so库所在的软件包?(二百一十六)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

element-plus表格合并

要实现这样的表格&#xff0c; 怎么做呢&#xff1f; 甚至是这种三级的呢&#xff1f; 官网的案例也是通过这个方法进行配置的&#xff0c;也就是说表格长什么样&#xff0c;关键在怎么处理的方法上。 这是官网的方法&#xff0c;可参考拓展&#xff1a; const arraySpanMeth…

【JVM】JVM相关机制

1. JVM内存区域划分 1.1 内存区域划分简介 内存区域划分&#xff1a;实际上JVM也是一个进程&#xff0c;进程运行时需要向操作系统申请一些系统资源&#xff08;内存就是典型的资源&#xff09;&#xff0c;这些内存空间就支撑着后续Java程序的运行&#xff0c;而这些内存又会…

Python环境搭建:一站式指南

在当前AIGC技术蓬勃发展的背景下&#xff0c;Python作为人工智能领域最受青睐的编程语言之一&#xff0c;成为我们必须掌握的技能。因此&#xff0c;搭建一个适合自己的Python环境成为了每个Python开发者的首要任务。本文将为您提供一站式的Python环境搭建指南&#xff0c;帮助…

力扣精选算法100道——颜色分类(双指针和三指针俩种方法解决此题)

目录 &#x1f6a9;了解题意 &#x1f6a9;算法分析 第一种方法&#xff1a;双指针 &#x1f6a9;代码实现一 第二种方法&#xff1a;三指针 &#x1f6a9;代码实现二 &#x1f6a9;了解题意 本题将整数0&#xff0c;1&#xff0c;2代表红白篮&#xff0c;nums中的整数并…

仿牛客网项目---私信列表和发送列表功能的实现

这篇文章我们来讲一下我的这个项目的另外一个功能&#xff1a;私信列表和发送列表功能。 先来设计DAO层。 Mapper public interface MessageMapper {// 查询当前用户的会话列表,针对每个会话只返回一条最新的私信.List<Message> selectConversations(int userId, int of…

【激光SLAM】基于已知位姿的构图算法 (Grid-based)

文章目录 地图分类概念 覆盖栅格建图算法栅格地图的特征数学描述假设 算法流程激光雷达的逆观测模型 计数(Count Model)建图算法概念数学描述观测模型地图估计 地图分类 概念 地图即为环境的空间模型。环境地图是机器人进行定位和规划的前提。定位可以用特征地图&#xff08;…