【数据结构】图的存储(邻接矩阵与邻接表)

news2025/6/2 23:30:15

图的存储结构

因为图中既有节点,又有边(节点与节点之间的关系),因此,在图的存储中,只需要保存:节点和边关系即可。

节点保存比较简单,只需要一段连续空间即可,那边关系该怎么保存呢?

1. 邻接矩阵

第一种存储结构---->邻接矩阵

邻接矩阵如何保存图的顶点和边呢?

因为节点与节点之间的关系就是连通与否,即为0或者1,因此邻接矩阵(二维数组)即是:

先用一个数组将顶点保存,然后采用矩阵来表示节点与节点之间的关系(边)

比如:

值为1就表示对应的这两个顶点是连通的,为0就表示两个顶点不连通

那其实观察上面的图我们可以发现:

无向图的邻接矩阵是对称的,第i行(列)元素之和,就是顶点i的度(边没有权值,只存0/1的情况下,元素和就是度)

有向图的邻接矩阵则不一定是对称的,第i行(列)元素之和就是顶点i 的出(入)度(边没有权值,只存0/1的情况下)

另外呢:

1. 如果边带有权值,并且两个节点之间是连通的,上图中的边的关系(1/0)就用权值代替,如果两个顶点不通,可以使用无穷大代替(后面我们实现的时候就要增加一个表示无穷大的模板参数)

2. 用邻接矩阵存储图的优点是能够快速知道两个顶点是否连通,取到权值

3. 缺陷是如果顶点比较多,边比较少时,矩阵中存储了大量的0成为系数矩阵,比较浪费空间;所以邻接矩阵比较适合存储稠密图(边比较多的图),不适合存储稀疏图(边比较少的图) 而且要求两个节点之间的路径不好求; 还有求一个顶点相连的顶点有哪些也不好求(O(N),这个用后面的邻接表结构存储的话就很好求)。

 在写邻接矩阵存储的代码时遇到了一个值得学习的bug,下面的bug怎么修改

template<class V, class W, W MAX_W = INT_MAX, bool Direction>
//                          参数3有默认值 ↗      ↗ 参数4无默认值

参数 3 (MAX_W) 有默认值,但参数 4 (Direction) 没有。这违反了 C++ 标准。

所以我们把bool Direction移到 这个  W MAX_W = INT_MAX前面就好了,或者添加在后面添加一下默认参数。

接下来我实现一下,下图中这个例子: 

namespace Adjacency_matrix
{
	// V接受顶点(vertex)的类型,W接受权值的类型,Direction(false无向图,true有向图)
	template<class V, class W, bool Direction, W MAX_W = INT_MAX>// 有权值的情况下不连通的边权值存INT_MAX
	class Graph
	{
	public:
		// "0123" 4 
		Graph(const V* ver, size_t n)
		{
			// 存储顶点与下标建立映射
			_vertexs.reserve(n);
			for (int i = 0; i < n; i++)
			{
				_vertexs.push_back(ver[i]);
				_indexMap[ver[i]] = i;
			}
			//给邻接矩阵开空间
			_matrix.resize(n); // 外向量n大小
			for (auto& e : _matrix)
			{
				e.resize(n, MAX_W); // 内向量n大小
			}
		}

		size_t GetVertexIndex(const V& v)
		{
			auto it = _indexMap.find(v);
			if (it != _indexMap.end())
			{
				return it->second;
			}
			else
			{
				cout << "顶点不存在" << endl;
				return -1;
			}
		}

		void AddEdge(const V& src, const V& dst, const W& w) // src起始顶点,dst终止顶点,w权值
		{
			size_t srci = GetVertexIndex(src);
			size_t dsti = GetVertexIndex(dst);
			// 添加一条边为A->B,B->A
			_matrix[srci][dsti] = w;
			if (Direction == false) // 无向图
			{
				_matrix[dsti][srci] = w;
			}
		}
		void DellEdge(const V& src, const V& dst)
		{
			size_t srci = GetVertexIndex(src);
			size_t dsti = GetVertexIndex(dst);
			// 删除一条边为A->B,B->A;
			_matrix[srci][dsti] = 0;
			if (Direction == false) // 无向图
			{
				_matrix[dsti][srci] = 0;
			}
		}

		void Print()
		{
			// 打印顶点和下标映射关系
			for (size_t i = 0; i < _vertexs.size(); ++i)
			{
				cout << _vertexs[i] << "--" << i << "  ";
			}
			cout << endl << 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] != MAX_W)
						cout << _matrix[i][j] << " ";
					else
						cout << "#" << " ";
				}
				cout << endl;
			}
			cout << endl << endl;
		}
	private:
		vector<V> _vertexs; // 顶点集合
		vector<vector<W>> _matrix;//存储边的矩阵
		map<V, int> _indexMap; //顶点和下标建立映射
	};

	void test()
	{
		Graph<char, int, false, INT_MAX> g("ABCD", 4);
		g.AddEdge('A', 'B', 1);
		g.AddEdge('B', 'C', 2);
		g.AddEdge('C', 'D', 3);
		g.AddEdge('D', 'A', 4);
		g.DellEdge('D', 'A');
		g.Print();
	}
}

结果展示: 

 

 2. 邻接表 

使用数组存储顶点的集合,使用链表存储顶点的关系(边)。

比如

无向图邻接表存储

一个顶点与哪些顶点相连,相连的顶点就存到这个顶点对应的链表中,当然如果带权的话也要存上对应边的权值。 (每个顶点都有一个对应的链表,多条链表用一个指针数组就可以维护起来)

注意:无向图中同一条边在邻接表中出现了两次。如果想知道顶点vi的度,只需要知道顶点vi 对应链表集合中结点的数目即可

有向图邻接表存储:

那通过上面的了解其实我们可以得出,对于邻接表的存储方式 

1. 适合存储稀疏图(边比较少的图),因为邻接表的话有多少边链表里面就存几个对应的顶点,不需要额外的空间;而上面邻接矩阵不论边多边少都要开一个N*N的矩阵(二维数组),边少的时候那就大部分位置都存的是0

2. 方便查找从一个顶点连接出去的边有哪些,因为它对应的边链表里面存的就是与这个顶点相连的顶点

3. 但是不方便确定两个顶点是否相连和获取权值(要遍历其中一个顶点的边链表查找O(N))

代码实现方式: 

namespace link_table
{
	template<class W>
	struct Edge
	{
		size_t _dsti; // 边的终止顶点下标
		W _w;	   // 权值
		Edge<W>* _next;

		Edge(const size_t& dsti, const W& w)
			:_dsti(dsti)
			,_w(w)
			,_next(nullptr)
		{}
	};
	// V接受顶点(vertex)的类型,W接受权值的类型,Direction(false无向图,true有向图)
	template<class V, class W, bool Direction = false>
	class Graph
	{
		typedef Edge<W> Edge;
	public:
		// "0123"  4
		Graph(const V* ver, size_t n)
		{
			//存储顶点并与下标建立映射
			_vertexs.reserve(n);
			for (size_t i = 0; i < n; i++)
			{
				_vertexs.push_back(ver[i]);
				_indexMap[ver[i]] = i;
			}
			// 给邻接表开空间
			_tables.resize(n, nullptr);
		}

		~Graph()
		{
			for (size_t i = 0; i < _tables.size(); i++)
			{
				if (_tables[i])
				{
					Edge* cur = _tables[i];
					Edge* next = cur;
					while (cur)
					{
						next = cur->_next;
						delete cur;
						cur = next;
					}
				}
			}
		}

		size_t GetVertexIndex(const V& v)
		{
			auto it = _indexMap.find(v);
			if (it != _indexMap.end())
			{
				return it->second;
			}
			else
			{
				cout << "顶点不存在" << endl;
				return -1;
			}
		}

		void AddEdge(const V& src, const V& dst, const W& w)
		{
			size_t srci = GetVertexIndex(src);
			size_t dsti = GetVertexIndex(dst);

			// src -> dst  A -> B
			Edge* eg = new Edge(dsti, w);
			eg->_next = _tables[srci];
			_tables[srci] = eg;

			// 如果是无向图,再添加反向边dst->src
			if (Direction == false)
			{
				Edge* eg = new Edge(srci, w);
				eg->_next = _tables[dsti];
				_tables[dsti] = eg;
			}
		}
		void DelEdge(const V& src, const V& dst)
		{
			del(src, dst);
			// 如果是无向图,再添加反向边dst->src
			if (Direction == false)
			{
				del(dst, src);
			}
		}

		void Print()
		{
			// 打印顶点
			for (size_t i = 0; i < _vertexs.size(); ++i)
			{
				cout << "[" << i << "]" << "->" << _vertexs[i] << endl;
			}
			cout << endl;
			// 遍历打印每个顶点的边链表
			for (size_t i = 0; i < _tables.size(); ++i)
			{
				cout << _vertexs[i] << "[" << i << "]->";
				Edge* cur = _tables[i];
				while (cur)
				{
					cout << "[" << _vertexs[cur->_dsti] << ":" << cur->_dsti << ":" << cur->_w << "]->";
					cur = cur->_next;
				}
				cout << "nullptr" << endl;
			}
		}
	private:
		void del(const V& src, const V& dst)
		{
			size_t srci = GetVertexIndex(src);
			size_t dsti = GetVertexIndex(dst);

			// A -> B -> C 删除A->B
			// 1.先判断这个边在不在,如果在就删除,不在就返回
			Edge* cur = _tables[srci];
			Edge* per = cur;
			while (cur)
			{
				// 如果找到了
				if (cur->_dsti == dsti)
				{
					// 如果在头
					if (per == cur)
					{
						_tables[srci] = per->_next;
						delete cur;
						break;
					}
					else
					{
						// 如果在尾和中间
						per->_next = cur->_next;
						delete cur;
						break;
					}
				}
				// 如果没找到
				per = cur;
				cur = cur->_next;
			}
		}
	private:
		vector<V> _vertexs;		// 顶点集合
		map<V, size_t> _indexMap;	// 顶点和下标建立映射
		vector<Edge*> _tables;	// 邻接表
	};

	void Test2()
	{
		string a[] = {"张三","李四","王五","赵六"};
		Graph<string, size_t> g1(a, 4);
		g1.AddEdge("张三", "李四", 100);
		g1.AddEdge("张三", "王五", 100);
		g1.AddEdge("张三", "赵六", 100);
		g1.AddEdge("王五", "赵六", 100);
		g1.Print();
	}
}

结果展示: 

 

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

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

相关文章

tomcat yum安装

使用yum安装 yum install -y java-1.7.0-openjdk* tomcat* --disablerepoepel## java-1.7.0-openjdk* 注意&#xff1a;最终安装的是java-1.8.0版本## --disablerepoepel 禁用&#xff1a;EPEL源&#xff0c;防止版本冲突 java -version (2) 启停&#xff1a;Tomcat 7 s…

从翻译后修饰角度解析人工合成途径与底盘细胞的适配性-文献精读136

Compatibility between synthetic pathway and chassis cells from the viewpoint of post-translational modifications 从翻译后修饰角度解析人工合成途径与底盘细胞的适配性 摘要 揭示工程化设计的人工合成途径与底盘细胞整体代谢网络的交互作用及适配性机制是合成生物学研…

Cesium快速入门到精通系列教程一

一、打造第一个Cesium应用 1、官方渠道下载Cesium&#xff08;可选择历史版本&#xff09; ​​GitHub Releases页面​​ 访问 Cesium GitHub Releases&#xff0c;此处列出了所有正式发布的版本。 通过标签&#xff08;如 v1.95.0&#xff09;选择目标版本&#xff0c;下载…

[Windows] 剪映 视频编辑处理

附链接&#xff1a;夸克网盘分享&#xff08;点击蓝色字体自行保存下载&#xff09;

决策树 GBDT XGBoost LightGBM

一、决策树 1. 决策树有一个很强的假设&#xff1a; 信息是可分的&#xff0c;否则无法进行特征分支 2. 决策树的种类&#xff1a; 2. ID3决策树&#xff1a; ID3决策树的数划分标准是信息增益&#xff1a; 信息增益衡量的是通过某个特征进行数据划分前后熵的变化量。但是&…

stm32 / arduino TPL0401A使用教程

这是在给英国的一个学生讲课时用到的一个芯片&#xff0c;做一个dcdc的反馈电路&#xff0c;刚开始用的不是这个&#xff0c;后来发现国内这个芯片用的挺成熟&#xff0c;就选择了这个。 芯片说明 首先我买的是TPL0401A,我发现淘宝上卖的都是A&#xff0c;其实想用C&#xff0…

数据结构与算法之单链表面试题(新浪、百度、腾讯)

单链表面试题&#xff08;新浪、百度、腾讯&#xff09; 求单链表中的有效节点的个数 public int getCount(HeroNode head) {Hero1 cur head.getNext();int count 0;while(cur ! null) {count;cur cur.getNext();}return count;}查找单链表中的倒数第k个结点【新浪面试题】…

单板机8088C语言计划

计划将原来用汇编写的小程序&#xff0c;用C语言重新写一遍 计划2个月能完成 然后再试试&#xff0c;能不能用C写一下固件BootLoad 和一个类似Dos时代的Debug调试器

一周学会Pandas2之Python数据处理与分析-数据重塑与透视-pivot() - 透视 (长 -> 宽,有限制)

锋哥原创的Pandas2 Python数据处理与分析 视频教程&#xff1a; 2025版 Pandas2 Python数据处理与分析 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili pivot() 是 pandas 中用于数据重塑的核心方法&#xff0c;它将长格式数据转换为宽格式数据&#xff0c;与 melt() 方…

机器学习中无监督学习方法的聚类:划分式聚类、层次聚类、密度聚类

1.定义和特点 2.划分式聚类&#xff1a;K-Means 、 K-Medoids 3.层次聚类&#xff1a;树状图 4.密度聚类&#xff1a;DBSCAN 5.聚类的应用 一、定义和特点 机器学习中的无监督学习聚类是一种通过数据内在结构将样本分组的技术&#xff0c;无需预先标注的类别标签。 它的核心目…

【HW系列】—溯源与定位—Linux入侵排查

文章目录 一、Linux入侵排查1.账户安全2.特权用户排查&#xff08;UID0&#xff09;3.查看历史命令4.异常端口与进程端口排查进程排查 二、溯源分析1. 威胁情报&#xff08;Threat Intelligence&#xff09;2. IP定位&#xff08;IP Geolocation&#xff09;3. 端口扫描&#x…

CPO-BP+MOPSO,冠豪猪优化BP神经网络+多目标粒子群算法!(Matlab源码)

目录 效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.CPO-BPNSGA&#xff0c;冠豪猪优化BP神经网络粒子群算法&#xff01;&#xff08;Matlab完整源码和数据&#xff09;&#xff0c;冠豪猪算法优化BP神经网络的权值和阈值&#xff0c;运行环境Matlab2020b及以上。 多…

模块化设计,static和extern(面试题常见)

文章目录 一、函数的声明和定义1.1 单个文件1.2 多个文件1.3 static和extern1.3.1 static修饰局部变量1.3.2 static修饰全局变量1.3.3 static修饰函数 总结 一、函数的声明和定义 1.1 单个文件 一般我们在使用函数的时候&#xff0c;直接将函数写出来就使用了 题目:写一个函数…

【快速解决】数据库快速导出成sql文件

1、cmd直接打开 输入命令 mysqldump -u用户名 -p密码 数据库名 > 导出文件名.sql修改成自己mysql的用户名和密码&#xff0c;和要导出的数据库名称&#xff0c;给导出的文件起一个名字。 如图所示 这样就成功了。

LearnOpenGL-笔记-其十二

今天我们来将LearnOpenGL的高级光照部分彻底完结&#xff1a; Bloom 泛光是一个非常常见的用于改善图像质量的手段&#xff0c;其主要做法就是将某个高亮度区域的亮度向四周发善以实现该区域更亮的视觉效果&#xff08;因为显示器的亮度范围有限&#xff0c;需要通过泛光来体…

Namespace 命名空间的使用

名字空间&#xff1a;划分更多的逻辑空间&#xff0c;有效避免名字冲突的问题 1.什么是命名空间 名字命名空间 namespace 名字空间名 {...} // 名字空间 n1 域 namespace n1 {// 全局变量int g_money 0;void save(int money){g_money money;}void pay(int money){g_money - m…

mac 下安装Rust Toolchain(Nightly)

你可以用 Homebrew 安装 rustup&#xff0c;这是推荐的管理 Rust toolchain的 brew install rustup-init安装 Rust&#xff08;包含 rustup&#xff09; rustup-init安装过程中会让你选择安装那个&#xff0c;直接回车选择默认的即可 安装完成后&#xff0c;cargo, rustc, r…

学习STC51单片机22(芯片为STC89C52RCRC)

记住这个AT指令千万不要去脑子记&#xff0c;要用手册查 每日一言 努力不是为了感动谁&#xff0c;而是为了不辜负自己的野心。 硬件&#xff1a;ESP8266 wife模块 蓝牙&#xff0c;ESP-01s&#xff0c;Zigbee&#xff0c;NB-lot等通信模块都是基于AT指令的设计 老样子 我们用…

Linux --UDP套接字实现简单的网络聊天室

一、Server端的实现 1.1、服务端的初始化 ①、创建套接字&#xff1a; 创建套接字接口&#xff1a; #include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int socket(int domain, int type, int protocol); //1. 这是一个创建套接字的接…

word批量导出visio图

具体步骤 修改word格式打开VBA窗口插入代码运行代码 修改word格式 将word文档修改为docm格式 打开VBA窗口 打开开发工具VisualBasic项&#xff0c;如果没有右键在自定义功能区添加 插入代码 插入 -> 模块&#xff0c;代码如下&#xff1a; Sub ExportAllVisioDiagrams()D…