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

news2024/11/11 5:02:42

文章目录

  • 一、图的遍历
  • 二、广度优先遍历
    • 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.coloradmin.cn/o/1481384.html

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

相关文章

ChatGPT学习第四周

&#x1f4d6; 学习目标 ChatGPT实践操作 通过实际操作和练习&#xff0c;加深对ChatGPT功能的理解。 项目&#xff1a;创建一个ChatGPT应用案例 设计一个基于ChatGPT的小项目&#xff0c;将理论应用于实践。 ✍️ 学习活动 学习资料 《万字干货&#xff01;ChatGPT 从零完…

地图可视化绘制 | R-ggplot2 NC地图文件可视化

在推出两期数据分享之后&#xff0c;获取数据的小伙伴们也知道&#xff0c;数据格式都是NetCDF(nc) 格式网格数据&#xff0c;虽然我在推文分享中说明使用Python、R或者GIS类软件都是可以进行 处理和可视化绘制的&#xff0c;但是&#xff0c;还是有小伙伴咨询使用编程软件Pyth…

使用labelimg对YOLO数据进行标注

1.打开pycharm软件 2.在终端安装labelimg&#xff1a;pip install labelimg 3.软件启动后的界面如下&#xff1a; 4.标注格式&#xff1a;标注格式选择YOLO 5.点击Open Dir打开需要标注的路径。 6.然后点击Create RectBox&#xff0c;框出需要标注的物体。 7.在下图对话框中…

vue面试:MVVM、MVC、MVP的区别?

vue面试&#xff1a;MVVM、MVC、MVP的区别&#xff1f; MVVM、MVC、MVP是什么&#xff1f;&#xff08;1&#xff09;MVC&#xff08;2&#xff09;MVVM&#xff08;3&#xff09;MVP MVVM、MVC、MVP是什么&#xff1f; MVC、MVP 和 MVVM 是三种常见的软件架构设计模式&#x…

常用sql语句及其优化

文章目录 介绍常用sql语句1. 数据查询1.1 SELECT 语句1.2 DISTINCT 关键字1.3 WHERE 子句1.4 ORDER BY 子句1.5 LIMIT 关键字 2. 数据更新2.1 INSERT INTO 语句2.2 UPDATE 语句2.3 DELETE FROM 语句 3. 数据管理3.1 CREATE TABLE 语句3.2 ALTER TABLE 语句3.3 DROP TABLE 语句 …

gpt-3.5-turbo与星火认知大模型v3.5回答对比

创建kernel // Create a kernel with OpenAI chat completionKernel kernel Kernel.CreateBuilder().AddOpenAIChatCompletion(modelId:"使用的模型id" ,apiKey: "APIKey").Build();使用讯飞星火认知大模型的话&#xff0c;可以参考我这一篇文章&#xff…

qt5-入门-使用拖动方式创建Dialog

参考&#xff1a; C GUI Programming with Qt 4, Second Edition 本地环境&#xff1a; win10专业版&#xff0c;64位&#xff0c;Qt5.12 目录 实现效果基本流程逐步实操1&#xff09;创建和初始化子部件2&#xff09;把子部件放进布局中3&#xff09;设置tab顺序4&#xff09…

十八:Java8新特性

文章目录 01、Java8概述02、Java8新特性的好处03、并行流与串行流04、Lambda表达式4.1、Lambda表达式使用举例4.2、Lambda表达式语法的使用14.3、Lambda表达式语法的使用2 05、函数式(Functional)接口5.1、函数式接口的介绍5.2、Java内置的函数式接口介绍及使用举例 06、方法引…

Nodejs基于vue的个性化服装衣服穿搭搭配系统sprinboot+django+php

本个性化服装搭配系统主要根据用户数据信息&#xff0c;推荐一些适合的搭配穿搭&#xff0c;同时&#xff0c;用户也可自己扫描上传自身衣物以及输入存放位置&#xff0c;搭配后存储到“我的搭配”中&#xff0c;以便下次挑选&#xff0c;既可以节省搭配时间&#xff0c;也方便…

vue3 构建项目

一.使用vite构建&#xff1a; npm init vitelatest 项目名称 构建的项目模板 进入项目 cd 项目名称 安装项目依赖包 npm install 启动项目 npm run dev 二.使用vue脚手架构建&#xff1a; npm init vuelatest 后续基本差不多

安全防御(第六次作业)

攻击可能只是一个点&#xff0c; 防御需要全方面进行 IAE引擎 DFI和DPI技术 --- 深度检测技术 DPI --- 深度包检测技术 --- 主要针对完整的数据包&#xff08;数据包分片&#xff0c;分段需要重组&#xff09; &#xff0c;之后对 数据包的内容进行识别。&#xff08;应用层&a…

mock工具whistle使用笔记

1、下载安装地址&#xff1a;关于whistle GitBook 安装完后&#xff0c;用本地的ip&#xff1a;设置的端口就可以反问&#xff0c;端口默认的8899&#xff0c;可以自定义 2、抓包https&#xff1a; &#xff08;1&#xff09;打开https &#xff08;2&#xff09;下载证书&…

从8.8到9.9,涨价的库迪还能守住牌局吗?

作者 | 辰纹 来源 | 洞见新研社 历经超半年的9.9元活动后&#xff0c;瑞幸不仅牢牢守稳盈利态势&#xff0c;还一举创造了新的神话——中国地区年收入首超星巴克。 根据瑞幸咖啡发布的截至12月31日的2023年第四季度及全年财报。第四季度&#xff0c;瑞幸咖啡净营收为70.6亿元…

Talk|上海交通大学晋嘉睿:序列建模技术在推荐系统中的应用

本期为TechBeat人工智能社区第574期线上Talk。 北京时间2月28日(周三)20:00&#xff0c;上海交通大学博士生—晋嘉睿的Talk已准时在TechBeat人工智能社区开播&#xff01; 他与大家分享的主题是: “序列建模技术在推荐系统中的应用”&#xff0c;系统地介绍了他们在序列数据的建…

C++数据结构与算法——二叉搜索树的属性

C第二阶段——数据结构和算法&#xff0c;之前学过一点点数据结构&#xff0c;当时是基于Python来学习的&#xff0c;现在基于C查漏补缺&#xff0c;尤其是树的部分。这一部分计划一个月&#xff0c;主要利用代码随想录来学习&#xff0c;刷题使用力扣网站&#xff0c;不定时更…

【数据库管理系统】Mysql 8.0.36入门级安装

下载地址 官方网址&#xff1a;MySQL 注意事项 建议不要安装最新版本&#xff0c;一般找mysql5.0或mysql8.0系列版本即可&#xff1b;mysq1官网有.zip和.msi两种安装形式&#xff1b;zip是压缩包&#xff0c;直接解压缩以后使用的&#xff0c;需要自己配置各种东西&#xff…

用按位或、按位与取反实现权限的增减

一、介绍&#xff1a; 在Linux操作系统中&#xff1a; r -4&#xff1a;可读权限 w -2&#xff1a;可写权限 x -1&#xff1a;可执行权限 问题1&#xff1a;三个权限为1,2,4&#xff0c;分别对应:2^0,2^1,2^2&#xff0c;为什么要用8进制表示用户的文件权限&#xff1f; …

Java毕业设计 基于SpringBoot vue 社区团购系统

Java毕业设计 基于SpringBoot vue 社区团购系统 SpringBoot vue 社区团购系统 功能介绍 前端用户: 首页 图片轮播 商品信息 商品分类展示 搜索 商品详情 点我收藏 添加到购物车 立即购买 我要开团 去参团 评论 公告资讯 资讯详情 登录 注册 个人中心 更新信息 点我充值 我的订…

三、《任务列表案例》前端程序搭建和运行

本章概要 整合案例介绍和接口分析 案例功能预览接口分析 前端工程导入 前端环境搭建导入前端程序 启动测试 3.1 整合案例介绍和接口分析 3.1.1 案例功能预览 3.1.2 接口分析 学习计划分页查询 /* 需求说明查询全部数据页数据 请求urischedule/{pageSize}/{currentPage} 请…

、JMETER与它的组件们

os进程取样器 这个取样器可以让jmeter直接调用python写的测试数据 这样就可以调用python写的测试数据给到jmeter进行调用 注意&#xff1a;1建议python返回转json格式dumps一下&#xff1b;2py文件中需要把结果打印出来&#xff0c;可以不用函数直接编写 传到jmeter之后可以用…