数据结构:图的存储和遍历

news2024/9/29 3:31:30

文章目录

  • 图的存储结构
    • 邻接矩阵
      • 邻接矩阵的存储模拟实现
    • 邻接表
      • 邻接表的模拟实现
  • 图的遍历
    • DFS和BFS遍历
  • 图的存储结构和遍历的实现

图也是一种数据结构,在实际生活中有广泛运用,因此本篇总结的就是图的存储等

图的存储结构

在图中既有节点,也有边,因此要存储的就是节点和边的关系,节点的存储只需要放在一个数组中就可以,那边的存储是如何进行存储的?

邻接矩阵

利用一个二维数组来存储,对于任意一个不越界的下标ij来说,arr[i][j]表示的是i和j这两个点之间的关系,如果是0表示的是从ij没有关系,如果是1表示的是它们之间存在边

在这里插入图片描述
如果这个这个图是带有权值的,那么边的关系就使用权值来代替,如果顶点之间没有关系,就用无穷来表示即可

在这里插入图片描述
缺点

用邻接矩阵存储图的有点是能够快速知道两个顶点是否连通,缺陷是如果顶点比较多,边比较少时,矩阵中存储了大量的0成为系数矩阵,比较浪费空间,并且要求两个节点之间的路径不是很好求

邻接矩阵的存储模拟实现

// 顶点的类型,权值的类型,权值的最大值,是否是有向图
template<class V, class W, W W_MAX = INT_MAX, bool Direction = false>
class Graph
{
public:
	Graph(const V* vertexs, size_t n)
	{
		// 建立顶点和下标的映射关系
		for (size_t i = 0; i < n; i++)
		{
			_vertexs.push_back(vertexs[i]);
			_vIndexMap[vertexs[i]] = i;
		}

		_matrix.resize(n, vector<W>(n, W_MAX));
	}

	// 给一个顶点取出来它对应的下标
	size_t GetVertexsIndex(const V& vertex)
	{
		auto it = _vIndexMap.find(vertex);
		if (it != _vIndexMap.end())
		{
			return it->second;
		}
		else
		{
			throw invalid_argument("不存在的顶点");
			return -1;
		}
	}

	// 给邻接矩阵加值
	void _AddEdge(size_t srci, size_t dsti, W w)
	{
		_matrix[srci][dsti] = w;
		if (Direction == false)
			_matrix[dsti][srci] = w;
	}

	// 加边
	void AddEdge(const V& src, const V& dst, W w)
	{
		size_t srci = GetVertexsIndex(src);
		size_t dsti = GetVertexsIndex(dst);
		_AddEdge(srci, dsti, w);
	}

	// 显示邻接矩阵
	void Print()
	{
		// 打印顶点和下标映射关系
		for (size_t i = 0; i < _vertexs.size(); ++i)
		{
			cout << _vertexs[i] << "-" << i << " ";
		}
		cout << endl << endl;
		cout << "  ";
		for (size_t i = 0; i < _vertexs.size(); ++i)
		{
			cout << i << " ";
		}
		cout << endl;
		// 打印矩阵
		for (size_t i = 0; i < _matrix.size(); ++i)
		{
			cout << i << " ";
			for (size_t j = 0; j < _matrix[i].size(); ++j)
			{
				if (_matrix[i][j] != W_MAX)
					cout << _matrix[i][j] << " ";
				else
					cout << "#" << " ";
			}
			cout << endl;
		}
		cout << endl << endl;
		// 打印所有的边
		for (size_t i = 0; i < _matrix.size(); ++i)
		{
			for (size_t j = 0; j < _matrix[i].size(); ++j)
			{
				if (i < j && _matrix[i][j] != W_MAX)
				{
					cout << _vertexs[i] << "-" << _vertexs[j] << ":" <<
						_matrix[i][j] << endl;
				}
			}
		}
	}
private:
	// 下标到顶点的映射
	vector<V> _vertexs;
	// 邻接矩阵的存储
	vector<vector<W>> _matrix;
	// 顶点到下标的映射
	map<V, size_t> _vIndexMap;
};

整体来说难度并不大,只是单纯的进行存储,注意map的使用是为了让顶点和下标建立一层映射,便于快速根据顶点可以定位到下标

邻接表

邻接表是另外一种图的存储形式,它适用于稀疏图,主要是借助链表来表示边和边的关系

无向图的存储

在这里插入图片描述
邻接表的好处也就体现出来了,想要知道A点和哪个点相连,遍历一下A所在的链表就可以知道了,而不需要在邻接矩阵中从头到尾进行遍历一次

有向图的存储

在这里插入图片描述
有了基本的原理支持,下面开始进行邻接表的模拟实现

邻接表的模拟实现

namespace list_table
{
	// 对于边的结构来说,需要存储的有边的两个顶点,边的权值,以及链接属性
	template <class W>
	struct Edge
	{
		Edge() = default;
		Edge(size_t srci, size_t dsti, W w)
			:_srci(srci)
			, _dsti(dsti)
			, _w(w)
			, _next(nullptr)
		{}
		size_t _srci;
		size_t _dsti;
		W _w;
		Edge<W>* _next;
	};

	// 顶点的类型,权值的类型,权值的最大值,是否是有向图
	template<class V, class W, bool Direction = false>
	class Graph
	{
		typedef Edge<W> Edge;
	public:
		Graph() = default;
		Graph(const V* vertexs, size_t n)
		{
			// 建立顶点和下标的映射关系
			for (size_t i = 0; i < n; i++)
			{
				_vertexs.push_back(vertexs[i]);
				_vIndexMap[vertexs[i]] = i;
			}

			_link_tables.resize(n, nullptr);
		}

		// 给一个顶点取出来它对应的下标
		size_t GetVertexsIndex(const V& vertex)
		{
			auto it = _vIndexMap.find(vertex);
			if (it != _vIndexMap.end())
			{
				return it->second;
			}
			else
			{
				throw invalid_argument("不存在的顶点");
				return -1;
			}
		}

		// 给邻接表加值
		void _AddEdge(size_t srci, size_t dsti, W w)
		{
			// 创建一个边的结构
			Edge* EdgePtr = new Edge(srci, dsti, w);
			Edge* tail = _link_tables[srci];
			while (tail && tail->_next)
				tail = tail->_next;
			// 把这个边的结构头插链入到邻接表
			if (tail == nullptr)
			{
				_link_tables[srci] = EdgePtr;
			}
			else
			{
				tail->_next = EdgePtr;
				tail = tail->_next;
			}
		}

		// 加边
		void AddEdge(const V& src, const V& dst, W w)
		{
			size_t srci = GetVertexsIndex(src);
			size_t dsti = GetVertexsIndex(dst);
			_AddEdge(srci, dsti, w);
			// 如果是无向图,就把对应的边也加到邻接表中
			if (Direction == false)
			{
				_AddEdge(dsti, srci, w);
			}
		}

		// 显示邻接矩阵
		void Print()
		{
			// 打印顶点和下标映射关系
			for (size_t i = 0; i < _vertexs.size(); ++i)
			{
				cout << _vertexs[i] << "-" << i << " ";
			}
			cout << endl;
			cout << endl;
			// 打印邻接表
			for (size_t i = 0; i < _link_tables.size(); ++i)
			{
				cout << i << " ";
				Edge* cur = _link_tables[i];
				while (cur)
				{
					cout << "[" << cur->_srci << "->" << cur->_dsti << "]" << "w->" << cur->_w << " ";
					cur = cur->_next;
				}
				cout << endl;
				cout << endl;
			}
			cout << endl << endl;
		}
	private:
		// 下标到顶点的映射
		vector<V> _vertexs;
		// 邻接表的存储
		vector<Edge*> _link_tables;
		// 顶点到下标的映射
		map<V, size_t> _vIndexMap;
	};
}

图的遍历

图的遍历主要有深度优先和广度优先,关于这两个遍历已经在之前的文章中总结过了,下面直接对这两个方法进行实现,这里是利用邻接矩阵进行遍历

DFS和BFS遍历

		// 图的BFS遍历,从某个顶点开始遍历
		void BFS(const V& Vertex)
		{
			queue<V> qe;
			qe.push(Vertex);
			size_t d = 1;
			vector<bool> visited(_matrix.size(), false);
			while (!qe.empty())
			{
				size_t dsize = qe.size();
				cout << Vertex << "的" << d << "度好友:";
				while (dsize--)
				{
					V front = qe.front();
					qe.pop();
					size_t index = GetVertexsIndex(front);
					visited[index] = true;
					for (size_t i = 0; i < _matrix.size(); i++)
					{
						if (_matrix[index][i] != W_MAX && visited[i] == false)
						{
							//找到了
							visited[i] = true;
							cout << _vertexs[i] << " ";
							qe.push(_vertexs[i]);
						}
					}
				}
				cout << endl;
				d++;
			}
		}

		// 图的DFS遍历,从某个顶点开始遍历
		void DFS(const V& Vertex)
		{
			size_t index = GetVertexsIndex(Vertex);
			vector<bool> visited(_vertexs.size(), false);
			_DFS(index, visited);
		}

		void _DFS(size_t index, vector<bool>& visited)
		{
			visited[index] = true;
			cout << _vertexs[index] << " ";
			for (size_t i = 0; i < _vertexs.size(); i++)
			{
				if (_matrix[index][i] != W_MAX && visited[i] == false)
				{
					_DFS(i, visited);
				}
			}
		}

图的存储结构和遍历的实现

#include <iostream>
#include <vector>
#include <map>
#include <queue>
#include <stack>
using namespace std;

namespace matrix
{
	// 顶点的类型,权值的类型,权值的最大值,是否是有向图
	template<class V, class W, W W_MAX = INT_MAX, bool Direction = false>
	class Graph
	{
	public:
		Graph(const V* vertexs, size_t n)
		{
			// 建立顶点和下标的映射关系
			for (size_t i = 0; i < n; i++)
			{
				_vertexs.push_back(vertexs[i]);
				_vIndexMap[vertexs[i]] = i;
			}

			_matrix.resize(n, vector<W>(n, W_MAX));
		}

		// 给一个顶点取出来它对应的下标
		size_t GetVertexsIndex(const V& vertex)
		{
			auto it = _vIndexMap.find(vertex);
			if (it != _vIndexMap.end())
			{
				return it->second;
			}
			else
			{
				throw invalid_argument("不存在的顶点");
				return -1;
			}
		}

		// 给邻接矩阵加值
		void _AddEdge(size_t srci, size_t dsti, W w)
		{
			_matrix[srci][dsti] = w;
			if (Direction == false)
				_matrix[dsti][srci] = w;
		}

		// 加边
		void AddEdge(const V& src, const V& dst, W w)
		{
			size_t srci = GetVertexsIndex(src);
			size_t dsti = GetVertexsIndex(dst);
			_AddEdge(srci, dsti, w);
		}

		// 显示邻接矩阵
		void Print()
		{
			// 打印顶点和下标映射关系
			for (size_t i = 0; i < _vertexs.size(); ++i)
			{
				cout << _vertexs[i] << "-" << i << " ";
			}
			cout << endl << endl;
			cout << "  ";
			for (size_t i = 0; i < _vertexs.size(); ++i)
			{
				cout << i << " ";
			}
			cout << endl;
			// 打印矩阵
			for (size_t i = 0; i < _matrix.size(); ++i)
			{
				cout << i << " ";
				for (size_t j = 0; j < _matrix[i].size(); ++j)
				{
					if (_matrix[i][j] != W_MAX)
						cout << _matrix[i][j] << " ";
					else
						cout << "#" << " ";
				}
				cout << endl;
			}
			cout << endl << endl;
			// 打印所有的边
			for (size_t i = 0; i < _matrix.size(); ++i)
			{
				for (size_t j = 0; j < _matrix[i].size(); ++j)
				{
					if (i < j && _matrix[i][j] != W_MAX)
					{
						cout << _vertexs[i] << "-" << _vertexs[j] << ":" <<
							_matrix[i][j] << endl;
					}
				}
			}
		}

		// 图的BFS遍历,从某个顶点开始遍历
		void BFS(const V& Vertex)
		{
			queue<V> qe;
			qe.push(Vertex);
			size_t d = 1;
			vector<bool> visited(_matrix.size(), false);
			while (!qe.empty())
			{
				size_t dsize = qe.size();
				cout << Vertex << "的" << d << "度好友:";
				while (dsize--)
				{
					V front = qe.front();
					qe.pop();
					size_t index = GetVertexsIndex(front);
					visited[index] = true;
					for (size_t i = 0; i < _matrix.size(); i++)
					{
						if (_matrix[index][i] != W_MAX && visited[i] == false)
						{
							//找到了
							visited[i] = true;
							cout << _vertexs[i] << " ";
							qe.push(_vertexs[i]);
						}
					}
				}
				cout << endl;
				d++;
			}
		}

		// 图的DFS遍历,从某个顶点开始遍历
		void DFS(const V& Vertex)
		{
			size_t index = GetVertexsIndex(Vertex);
			vector<bool> visited(_vertexs.size(), false);
			_DFS(index, visited);
		}

		void _DFS(size_t index, vector<bool>& visited)
		{
			visited[index] = true;
			cout << _vertexs[index] << " ";
			for (size_t i = 0; i < _vertexs.size(); i++)
			{
				if (_matrix[index][i] != W_MAX && visited[i] == false)
				{
					_DFS(i, visited);
				}
			}
		}

	private:
		// 下标到顶点的映射
		vector<V> _vertexs;
		// 邻接矩阵的存储
		vector<vector<W>> _matrix;
		// 顶点到下标的映射
		map<V, size_t> _vIndexMap;
	};
}

namespace list_table
{
	// 对于边的结构来说,需要存储的有边的两个顶点,边的权值,以及链接属性
	template <class W>
	struct Edge
	{
		Edge() = default;
		Edge(size_t srci, size_t dsti, W w)
			:_srci(srci)
			, _dsti(dsti)
			, _w(w)
			, _next(nullptr)
		{}
		size_t _srci;
		size_t _dsti;
		W _w;
		Edge<W>* _next;
	};

	// 顶点的类型,权值的类型,权值的最大值,是否是有向图
	template<class V, class W, bool Direction = false>
	class Graph
	{
		typedef Edge<W> Edge;
	public:
		Graph() = default;
		Graph(const V* vertexs, size_t n)
		{
			// 建立顶点和下标的映射关系
			for (size_t i = 0; i < n; i++)
			{
				_vertexs.push_back(vertexs[i]);
				_vIndexMap[vertexs[i]] = i;
			}

			_link_tables.resize(n, nullptr);
		}

		// 给一个顶点取出来它对应的下标
		size_t GetVertexsIndex(const V& vertex)
		{
			auto it = _vIndexMap.find(vertex);
			if (it != _vIndexMap.end())
			{
				return it->second;
			}
			else
			{
				throw invalid_argument("不存在的顶点");
				return -1;
			}
		}

		// 给邻接表加值
		void _AddEdge(size_t srci, size_t dsti, W w)
		{
			// 创建一个边的结构
			Edge* EdgePtr = new Edge(srci, dsti, w);
			Edge* tail = _link_tables[srci];
			while (tail && tail->_next)
				tail = tail->_next;
			// 把这个边的结构头插链入到邻接表
			if (tail == nullptr)
			{
				_link_tables[srci] = EdgePtr;
			}
			else
			{
				tail->_next = EdgePtr;
				tail = tail->_next;
			}
		}

		// 加边
		void AddEdge(const V& src, const V& dst, W w)
		{
			size_t srci = GetVertexsIndex(src);
			size_t dsti = GetVertexsIndex(dst);
			_AddEdge(srci, dsti, w);
			// 如果是无向图,就把对应的边也加到邻接表中
			if (Direction == false)
			{
				_AddEdge(dsti, srci, w);
			}
		}

		// 显示邻接矩阵
		void Print()
		{
			// 打印顶点和下标映射关系
			for (size_t i = 0; i < _vertexs.size(); ++i)
			{
				cout << _vertexs[i] << "-" << i << " ";
			}
			cout << endl;
			cout << endl;
			// 打印邻接表
			for (size_t i = 0; i < _link_tables.size(); ++i)
			{
				cout << i << " ";
				Edge* cur = _link_tables[i];
				while (cur)
				{
					cout << "[" << cur->_srci << "->" << cur->_dsti << "]" << "w->" << cur->_w << " ";
					cur = cur->_next;
				}
				cout << endl;
				cout << endl;
			}
			cout << endl << endl;
		}
	private:
		// 下标到顶点的映射
		vector<V> _vertexs;
		// 邻接表的存储
		vector<Edge*> _link_tables;
		// 顶点到下标的映射
		map<V, size_t> _vIndexMap;
	};
}

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

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

相关文章

【docker 】Dockerfile指令学习

学习文档地址 上篇文章&#xff1a;【docker 】基于Dockerfile创建镜像 Dockerfile指令文档地址 .dockerignore 文件 Dockerfile指令 常见的指令 Dockerfile 指令说明FROM指定基础镜像&#xff0c;用于后续的指令构建。MAINTAINER指定Dockerfile的作者/维护者。&#xff…

代码审计零基础入门之思路篇

0x01 前言 ThinkPHP 是一款开源的 PHP 框架&#xff0c;用于快速、简单地开发 PHP 应用程序。它提供了一套丰富的功能和工具&#xff0c;使开发者能够更容易地构建各种规模的 Web 应用。ThinkPHP 的目标是提高开发效率&#xff0c;同时保持代码的可读性和可维护性。thinkphp的…

mysql:在字符串类型的列上创建索引,建议指定索引前缀长度

https://dev.mysql.com/doc/refman/8.2/en/create-index.html#create-index-column-prefixes 在字符串类型的列上创建索引&#xff0c;建议指定索引前缀长度&#xff0c;而没有必要用整个列来创建索引。因为用前面的字符创建索引&#xff0c;查询时并不会比在整列上创建索引慢很…

绿盟 SAS堡垒机 local_user.php 权限绕过漏洞复现

0x01 产品简介 SAS 安全审计系统是绿盟科技开发的一款堡垒机。 0x02 漏洞概述 绿盟 SAS堡垒机 local_user.php接口处存在权限绕过漏洞,未经身份认证的攻击者可以访问他们通常无权访问的敏感资源,最终导致系统处于极度不安全状态。 0x03 复现环境 FOFA: body="/ne…

土壤科学灌溉CG-36 土壤水势传感器

土壤科学灌溉CG-36 土壤水势传感器产品概述 土壤水势传感器可以很方便地插入到土壤剖面坑中&#xff0c;在其周围包裹上湿土即可。测定和记录非常简单。免维护、无需校准即可测量较大范围的土壤水势&#xff1b;无需灌水&#xff0c;大量程使得它成为测量自然系统水势的理想传…

javacv的视频截图功能

之前做了一个资源库的小项目&#xff0c;因为上传资源文件包含视频等附件&#xff0c;所以就需要时用到这个功能。通过对视频截图&#xff0c;然后作为封面缩略图&#xff0c;达到美观效果。 首先呢&#xff0c;需要准备相关的jar包&#xff0c;之前我用的是低版本的1.4.2&…

spring boot 实现直播聊天室(二)

spring boot 实现直播聊天室(二) 技术方案: spring bootnettyrabbitmq 目录结构 引入依赖 <dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId><version>4.1.96.Final</version> </dependency>Si…

位1的个数

题目链接 位1的个数 题目描述 注意点 输入必须是长度为 32 的 二进制串 解答思路 位运算判断每一位是否为1 代码 public class Solution {// you need to treat n as an unsigned valuepublic int hammingWeight(int n) {int res 0;for (int i 0; i < 32; i) {res …

Mac中nvm切换node版本失败

Mac中使用 nvm 管理 node 版本&#xff0c;在使用指令&#xff1a;nvm use XXX 切换版本之后。 关闭终端&#xff0c;再次打开&#xff0c;输入 node -v 还是得到之前的 node 版本。 原因&#xff1a; 在这里这个 default 中有个 node 的版本号&#xff0c;使用 nvm use 时&a…

【玩转TableAgent数据智能分析】会话式数据分析,所需即所得!

目录 1 TableAgent介绍 2 TableAgent五大优点 3 体验TableAgent 3.1 登录TableAgent平台 3.2 会话式数据分析 4 总结 【优化改善】 【对比TableAgent与文心一言- E言易图】 1 TableAgent介绍 TableAgent是一款数据集成和分析平台&#xff0c;它可以帮助用户从多个数据源中…

【wimdows电脑上管理员账户与管理员身份的区别】

管理员账户 在控制面板的用户账户中&#xff0c;点击更改账户类型&#xff0c;可以看到目前的账户是“管理员账户”还是“标准账户”。 管理员身份 在快捷方式上右击&#xff0c;可以看到&#xff0c;可以选择以管理员身份运行该软件。 如何查看某个应用是否以管理员身份…

大数据云计算之OpenStack

大数据云计算之OpenStack 1.什么是OpenStack&#xff0c;其作用是什么&#xff1f;OpenStack主要的组成模块有哪些&#xff1f;各自的主要作用是什么&#xff1f; OpenStack是一个开源的云计算平台&#xff0c;旨在为企业和服务提供商提供私有云和公有云的建设和管理解决方案…

MySQL第三方备份工具Percona XtraBackup

实验环境&#xff1a; CentOS7.9 准备软件&#xff1a;yum -y install https://repo.percona.com/yum/percona-release-latest.noarch.rpm 一、什么是Percona XtraBackup&#xff1a;Percona XtraBackup&#xff08;简称PXB&#xff09;是 Percona 公司开发的一个用于 MySQL …

Optional.ofNullable的使用

Optional.ofNullable的使用 Optional.ofNullable()方法是Java 8中的一个方法&#xff0c;用于创建一个Optional对象&#xff0c;该对象可能包含一个非空值&#xff0c;也可能为空。如果传递给ofNullable()方法的参数为null&#xff0c;则返回一个空的Optional对象&#xff0c;…

现代雷达车载应用——第2章 汽车雷达系统原理 2.3节 信号模型

经典著作&#xff0c;值得一读&#xff0c;英文原版下载链接【免费】ModernRadarforAutomotiveApplications资源-CSDN文库。 2.3 信号模型 雷达的发射机通常发出精心设计和定义明确的信号。然而&#xff0c;接收到的返回信号是多个分量的叠加&#xff0c;包括目标的反射、杂波…

conda环境报错: Solving environment: failed with initial frozen solve.

出现的情况&#xff1a; 解决方法&#xff1a; 参考了许多博客 建议的方法&#xff1a; 创建一个虚拟环境 conda create -n torch_1.3 python3.6 激活虚拟环境 conda activate torch_1.3 conda安装 conda install pytorch1.5.0 如果报错每个包单独安装就可以了&#x…

issue unit

The Issue Unit issue queue用来hold住&#xff0c;已经dispatched&#xff0c;但是还没有执行的uops&#xff1b; 当一条uop的所有的operands已经ready之后&#xff0c;request请求会被拉起来&#xff1b;然后issue select logic将会从request bit 1的slot中&#xff0c;选择…

中通快递查询,中通快递单号查询,并进行多次揽收分析

批量查询中通快递单号的物流信息&#xff0c;并将其中的多次揽收件分析筛选出来。 所需工具&#xff1a; 一个【快递批量查询高手】软件 中通快递单号若干 操作步骤&#xff1a; 步骤1&#xff1a;运行【快递批量查询高手】软件&#xff0c;第一次使用的伙伴记得先注册&…

LeetCode 309买卖股票的最佳时机含冷冻期 714买卖股票的最佳时机含手续费 | 代码随想录25期训练营day51

动态规划算法9 LeetCode 309 买卖股票的最佳时机含冷冻期 2023.12.14 题目链接代码随想录讲解[链接] int maxProfit(vector<int>& prices) {//1确定dp二维数组//dp[i][0]表示遍历到第i天时持有股票的当前收入;dp[i][1]表示遍历到第i天时未持有股票的当前收入//dp…

项目管理:如何把项目管理落实到执行细节

一切皆项目&#xff0c;我们的人生也是一个大的项目&#xff0c;在这个大的项目中&#xff0c;拥有多个小的项目&#xff0c;多个阶段&#xff0c;各种活动&#xff0c;小的项目构建成不同大的项目。 对于个人这个项目&#xff0c;我们可以一步步去完成&#xff0c;从项目管理…