图的概念即存储结构(C++实现图【1】)

目录

1. 图的基本概念

2. 图的存储结构

2.1 邻接矩阵

2.1.1私有成员变量

2.1.2类模板的声明

2.1.3构造函数

2.1.4获取顶点下标

2.1.5添加边的信息

2.1.6打印图

2.1.7测试用例

2.2邻接表

2.2.1私有成员变量

2.2.2Edge类

2.2.3类模板的声明

2.2.4构造函数

2.2.5获取顶点下标

2.2.6添加边的信息

2.2.7打印图

2.2.8测试用例


1. 图的基本概念

图是由顶点集合及顶点间的关系组成的一种数据结构:G = (V, E),其中:

顶点集合V = {x|x属于某个数据对象集}是有穷非空集合;

E = {(x,y)|x,y属于V}或者E = {|x,y属于V && Path(x, y)}是顶点间关系的有穷集合,也叫做边的集合。

(x, y)表示x到y的一条双向通路,即(x, y)是无方向的;Path(x, y)表示从x到y的一条单向通路,即 Path(x, y)是有方向的。

顶点图中结点称为顶点,第i个顶点记作vi。两个顶点vi和vj相关联称作顶点vi和顶点vj之间有一条边,图中的第k条边记作ek,ek = (vi,vj)或<vi,vj>。

有向图和无向图:在有向图中,顶点对是有序的,顶点对<x,y>称为顶点x到顶点y的一条边(弧),<x,y>和<y,x>是两条不同的边,比如下图G3和G4为有向图。在无向图中,顶点对(x, y) 是无序的,顶点对(x,y)称为顶点x和顶点y相关联的一条边,这条边没有特定方向,(x, y)和(y,x) 是同一条边,比如下图G1和G2为无向图。注意:无向边(x, y)等于有向边<x,y>和<y,x>

完全图:在有n个顶点的无向图中,若有n * (n-1)/2条边,即任意两个顶点之间有且仅有一条边, 则称此图为无向完全图,比如上图G1;在n个顶点的有向图中,若有n * (n-1)条边,即任意两个顶点之间有且仅有方向相反的边,则称此图为有向完全图,比如上图G4。

邻接顶点:在无向图中G中,若(u, v)是E(G)中的一条边,则称u和v互为邻接顶点,并称边(u,v)依附于顶点u和v在有向图G中,若<u,v>是E(G)中的一条边,则称顶点u邻接到v,顶点v邻接自顶点u,并称边与顶点u和顶点v相关联

顶点的度:顶点v的度是指与它相关联的边的条数,记作deg(v)。在有向图中,顶点的度等于该顶点的入度与出度之和,其中顶点v的入度是以v为终点的有向边的条数,记作indev(v);顶点v的出度是以v为起始点的有向边的条数,记作outdev(v)。因此:dev(v) = indev(v) + outdev(v)。注意:对于无向图,顶点的度等于该顶点的入度和出度,即dev(v) = indev(v) = outdev(v)。

路径:在图G = (V, E)中,若从顶点vi出发有一组边使其可到达顶点vj,则称顶点vi到顶点vj的顶点序列为从顶点vi到顶点vj的路径

路径长度:对于不带权的图,一条路径的路径长度是指该路径上的边的条数;对于带权的图,一 条路径的路径长度是指该路径上各个边权值的总和

简单路径与回路:若路径上各顶点v1,v2,v3,…,vm均不重复,则称这样的路径为简单路 径。若路径上第一个顶点v1和最后一个顶点vm重合,则称这样的路径为回路或环

子图:设图G = {V, E}和图G1 = {V1,E1},若V1属于V且E1属于E,则称G1是G的子图

连通图:在无向图中,若从顶点v1到顶点v2有路径,则称顶点v1与顶点v2是连通的。如果图中任意一 对顶点都是连通的,则称此图为连通图

强连通图:在有向图中,若在每一对顶点vi和vj之间都存在一条从vi到vj的路径也存在一条从vj 到 vi的路径,则称此图是强连通图。

生成树:一个连通图最小连通子图称作该图的生成树。有n个顶点的连通图的生成树有n个顶点和n-1条边。

2. 图的存储结构

因为图中既有节点,又有边(节点与节点之间的关系),因此,在图的存储中,只需要保存:节点和边关系即可。节点保存比较简单,只需要一段连续空间即可,那边关系该怎么保存呢?

2.1 邻接矩阵

因为节点与节点之间的关系就是连通与否,即为0或者1,因此邻接矩阵(二维数组)即是:先用一个数组将定点保存,然后采用矩阵来表示节点与节点之间的关系

注意:

1. 无向图的邻接矩阵是对称的,第i行(列)元素之和,就是顶点i的度。有向图的邻接矩阵则不一 定是对称的,第i行(列)元素之后就是顶点i 的出(入)度。

2. 如果边带有权值,并且两个节点之间是连通的,上图中的边的关系就用权值代替,如果两个顶点不通,则使用无穷大代替。

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

2.1.1私有成员变量

	private:
	vector<V> _vertexs;//顶点集合
	map<V, int> _indexMap;//顶点映射下标
	vector<vector<W>> _matrix; //邻接矩阵

我们的私有成员变量有三个,一个是存储顶点的vector,一个是存储顶点与下标映射关系的map,还有一个就是存储边信息的二维数组。V和W是我们类模板声明出来的变量,分别代表顶点和边的类型。

2.1.2类模板的声明

template<class V,class W,W MAX_W = INT_MAX , bool Direction = false>

V,W我们已经说过了,MAX_W就是我们设置的无穷大变量,用来标识两点不连通时的情况,Direction变量的意思是我们的图是有向的还是无向的,我们给它个缺省值为false,默认它是无向的。

2.1.3构造函数

class Graph
	{
		typedef Graph<V, W, MAX_W, Direction> Self;
	public:
		//图的创建
		//1、IO输入 -- 不方便测试,oj更适合
		//2、图结构关系写到文件,读取文件
		//3、手动添加边
		Graph() = default;

		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);
			}
		}
    }

我们先将这个图重命名一下,方便我们后面使用,为了方便我们测试,我们采用的是手动添加边的方式。无参构造我们将它设置成默认,就是让编译器自己生成,然后我们的带参构造,我们需要把顶点数组里面的元素全部填入顶点vector里面,并且记录顶点与下标的映射关系,然后我们先将二维数组进行初始化,用MAX_W来表达他们未联通的状态。

2.1.4获取顶点下标

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

				return -1; 
			}
		}

这个操作很简单,我们只需要在map里面找就可以了,找到了就返回它的下标,找不到就说明这个参数无效,我们将它用std::invalid_argument类抛异常,错误描述信息为"顶点不存在"并返回-1。

2.1.5添加边的信息

void _AddEdge(size_t src, size_t dst, const W& w)
		{
			_matrix[src][dst] = w;
			//无向图
			if (Direction == false)
			{
				_matrix[dst][src] = w;
			}
		}

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

			_AddEdge(srci, dsti, w);
		}

_AddEdge是AddEdge的子函数,我们从下往上看,要想添加边,我们需要的参数是这条边的起点和终点,还有边的权值。在有了这些参数之后,我们要获得这两个顶点的下标,然后我们就可以在二维数组里面将他们的边的权值填上,倘若是无向的,那么我们还要把他们的起始点和终点的位置交换一下,再给他们也填一下边的权值。

2.1.6打印图

void Print()
		{
			//顶点
			for (size_t i = 0; i < _vertexs.size(); i++)
			{
				cout << "[" << i << "]" << "->" << _vertexs[i] << endl;
			}
			cout << endl;

			//打印下标
			cout << "  ";
			for (size_t i = 0; i < _vertexs.size(); ++i)
			{
				//cout << i << " ";
				printf("%4d", i);
			}
			cout << endl;
			
			//矩阵
			for (size_t i = 0; i < _matrix.size(); i++)
			{
				cout << i << " ";
				for (size_t j = 0; j < _matrix.size(); j++)
				{
					if (_matrix[i][j] == MAX_W)
					{
						//cout << "*" << " ";
						printf("%4c", '*');
					}
					else {
					//cout << _matrix[i][j] << " ";
						printf("%4d", _matrix[i][j]);
					}
				}cout << endl;
			}
			cout << endl;
		}

我们先将顶点与它下标的对应关系打印出来,然后我们打印矩阵,我们先将第一行、第一列用下标来填充,然后再将边的信息打印出来,不连通的点我们用符号'*'来代替。

2.1.7测试用例

matrix::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();

这里我们用的是有向图,运行截图如下:

2.2邻接表

邻接表:使用数组表示顶点的集合,使用链表表示边的关系。

1. 无向图邻接表存储

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

2. 有向图邻接表存储

注意:有向图中每条边在邻接表中只出现一次,与顶点vi对应的邻接表所含结点的个数,就 是该顶点的出度,也称出度表,要得到vi顶点的入度,必须检测其他所有顶点对应的边链 表,看有多少边顶点的dst取值是i。

我们就简单实现一下入边就可以了。

2.2.1私有成员变量

private:
		vector<V> _vertexs;//顶点集合
		map<V, int> _indexMap;//顶点映射下标
		vector<Edge*> _tables; //邻接表
	};

唯一不同的就是把邻接矩阵二维数组换成了vector链表。

2.2.2Edge类

template<class W>
	struct Edge
	{
		int _dsti;//目标点的下标
		W _w;//权值
		Edge<W>* _next;

		Edge(int dsti,const W& w)
			:_dsti(dsti)
			,_w(w)
			,_next(nullptr)
		{}
	};

Edge节点存的是我们边的信息,它包括我们的目标点下标,权值,next节点指针,我们再实现一下带参构造就可以了。

2.2.3类模板的声明

template<class V, class W, bool Direction = false>

这里我们就不需要最大值来填充不连通的点了,因为不连通的点根本就不会出现在我们的邻接表中。

2.2.4构造函数

typedef Edge<W> Edge;
	public:
		//图的创建
		//1、IO输入 -- 不方便测试,oj更适合
		//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;
			}
			_tables.resize(n);
		}

我们先将节点重命名一下,方便我们后续编写代码,带参构造中,我们先将顶点存好,下标映射关系对好,我们先将邻接表初始化空间。

2.2.5获取顶点下标

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

				return -1;
			}
		}

这个操作跟我们的邻接矩阵是一模一样的。

2.2.6添加边的信息

void AddEdge(const V& src, const V& dst, const W& w)
		{
			size_t srci = GetVertexIndex(src);
			size_t dsti = GetVertexIndex(dst);
			//1->2
			Edge* eg = new Edge(dsti, w);
			eg->_next = _tables[srci];
			_tables[srci] = eg;

			//2->1
			if (Direction == false)
			{
				Edge* eg = new Edge(srci, w);
				eg->_next = _tables[dsti];
				_tables[dsti] = eg;
			}
		}

我们还是要获取起点和终点的下标,然后创建节点,我们采用头插的方式将节点边的关系创建好,倘若是无向的,我们还要进行跟邻接矩阵同样的操作。

2.2.7打印图

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;
			}
		}

2.2.8测试用例

string a[] = { "张三", "李四", "王五", "赵六" };
	link_table::Graph<string, int,true> g1(a, 4);
	g1.AddEdge("张三", "李四", 100);
	g1.AddEdge("张三", "王五", 200);
	g1.AddEdge("王五", "赵六", 30);

运行截图如下:

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用Tauri创建桌面应用

当前是在 Windows 环境下 1.准备 系统依赖项 Microsoft C 构建工具WebView2 (Windows10 v1803 以上版本不用下载&#xff0c;已经默认安装了) 下载安装 Rust下载安装 Rust 需要重启终端或者系统 重新打开cmd&#xff0c;键入rustc --version&#xff0c;出现 rust 版本号&…

实验13 使用预训练resnet18实现CIFAR-10分类

1.数据预处理 首先利用函数transforms.Compose定义了一个预处理函数transform&#xff0c;里面定义了两种操作&#xff0c;一个是将图像转换为Tensor&#xff0c;一个是对图像进行标准化。然后利用函数torchvision.datasets.CIFAR10下载数据集&#xff0c;这个函数有四个常见的…

Wwise SoundBanks内存优化

1.更换音频格式为Vorbis 2.停用多余的音频&#xff0c;如Random Container的随机脚步声数量降为2个 3.背景音乐勾选“Stream”。这样就让音频从硬盘流送到Wwise&#xff0c;而不是保存在内存当中&#xff0c;也就节省了内存 4.设置最大发声数Max Voice Instances 5.设置音频…

【测试工具JMeter篇】JMeter性能测试入门级教程(六):JMeter中实现参数化的几种方式

一、参数化的定义 什么是参数化&#xff1f;从字面上去理解的话&#xff0c;就是事先准备好数据&#xff08;广义上来说&#xff0c;可以是具体的数据值&#xff0c;也可以是数据生成规则&#xff09;&#xff0c;而非在脚本中固化&#xff0c;脚本执行时从准备好的数据中取值。…

2024年11月份实时获取地图边界数据方法,省市区县街道多级联动【附实时geoJson数据下载】

首先&#xff0c;来看下效果图 在线体验地址&#xff1a;https://geojson.hxkj.vip&#xff0c;并提供实时geoJson数据文件下载 可下载的数据包含省级geojson行政边界数据、市级geojson行政边界数据、区/县级geojson行政边界数据、省市区县街道行政编码四级联动数据&#xff0…

【力扣】—— 二叉树的前序遍历、字典序最小回文串

Hi~&#xff01;这里是奋斗的明志&#xff0c;很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~~ &#x1f331;&#x1f331;个人主页&#xff1a;奋斗的明志 &#x1f331;&#x1f331;所属专栏&#xff1a;数据结构 &#x1f4da;本系列文章为个人学…

电脑显示没信号显示屏不亮怎么办?电脑没信号解决方法

电脑没信号显示屏不亮这种故障的原因可能有多种&#xff0c;例如显示器的供电、连接、设置等问题&#xff0c;或者电脑的显卡、内存、硬盘、主板等硬件问题。所以我们想要解决这个问题&#xff0c;也是需要多方面排除找到具体原因然后进行修复。下面将为大家介绍一些常见的电脑…

【docker】Windows11创建Ubuntu-desktop并使用VNC完成远程访问

【docker】Windows11创建Ubuntu-desktop并使用VNC完成远程访问 文章目录 【docker】Windows11创建Ubuntu-desktop并使用VNC完成远程访问前言创建Ubuntu容器下载镜像运行容器连接容器 搭建容器XFCE桌面环境安装ubuntu桌面 总结 前言 docker ubuntu容器在深度学习领域的使用过程…

歇一歇,写写段子

无聊的日子都在写段子1.0 中学的时候喜欢看意林之类的杂志&#xff0c; 里面的作者用乱七八糟的理由跑去旅游&#xff0c;然后说“阻碍你脚步的永远只有逃离的勇气和对生活的热爱”&#xff0c; 我觉得太对了&#xff0c;可惜 12306 付款方式里没有勇气和热爱&#xff0c;不…

1203论文速读

1、Hierarchical Stochastic Block Model for Community Detection in Multiplex Networks∗ &#xff08;多层网络社区检测的层次随机块模型 &#xff09; 全文总结&#xff1a;本文提出了一种新颖的贝叶斯模型&#xff0c;称为分层随机块模型&#xff08;HSBM&#xff09;&a…

双向长短期记忆(Bi-LSTM)神经网络介绍

长短期记忆(Long Short-Term Memory, LSTM)神经网络&#xff1a; 1.是Hochreiter和Schmidhuber设计的循环神经网络(Recurrent Neural Network, RNN)的改进版本。LSTM模型借鉴了人类大脑的选择性输入和选择性遗忘机制&#xff0c;获取序列中的关键信息&#xff0c;遗忘和当前预测…

.NET 9 中 LINQ 新增功能实现过程

本文介绍了.NET 9中LINQ新增功能&#xff0c;包括CountBy、AggregateBy和Index方法,并提供了相关代码示例和输出结果&#xff0c;感兴趣的朋友跟随我一起看看吧 LINQ 介绍 语言集成查询 (LINQ) 是一系列直接将查询功能集成到 C# 语言的技术统称。 数据查询历来都表示为简单的…

解决PowerPoint的流程图图标中输入文字位置偏下的问题

解决PowerPoint的流程图图标中输入文字位置偏下的问题 背景 在PowerPoint中&#xff0c;插入流程图形状&#xff0c;并在其内部输入中文字符&#xff0c;是很常规的操作。然而&#xff0c;有时输入文本发现文本整体偏下&#xff0c;靠近流程图下侧。 症状 文字位置偏下的效…

C++基础:list的基本使用

文章目录 1.基本构造和插入删除基本构造和尾插数据迭代器的分类内置排序sort任意位置插入删除 2.链表的合并,去重和剪切链表的合并链表去重链表的剪切 list的本质就是带头双向循环列表 1.基本构造和插入删除 基本构造和尾插数据 与之前vector的方法相同直接调用即可 迭代器的分…

SpringBoot中实现EasyExcel实现动态表头导入(完整版)

前言 最近在写项目的时候有一个需求&#xff0c;就是实现动态表头的导入&#xff0c;那时候我自己也不知道动态表头导入是什么&#xff0c;查询了大量的网站和资料&#xff0c;终于了解了动态表头导入是什么。 一、准备工作 确保项目中引入了处理 Excel 文件的相关库&#xff…

亚马逊云(AWS)使用root用户登录

最近在AWS新开了服务器&#xff08;EC2&#xff09;&#xff0c;用于学习&#xff0c;遇到一个问题就是默认是用ec2-user用户登录&#xff0c;也需要密钥对。 既然是学习用的服务器&#xff0c;还是想直接用root登录&#xff0c;下面开始修改&#xff1a; 操作系统是&#xff1…

基于Java Springboot武汉市公交路线查询APP且微信小程序

一、作品包含 源码数据库设计文档万字PPT全套环境和工具资源部署教程 二、项目技术 前端技术&#xff1a;Html、Css、Js、Vue、Element-ui 数据库&#xff1a;MySQL 后端技术&#xff1a;Java、Spring Boot、MyBatis 三、运行环境 开发工具&#xff1a;IDEA/eclipse 微信…

【C++】数组

1.概述 所谓数组&#xff0c;就是一个集合&#xff0c;该集合里面存放了相同类型的数据元素。 数组特点&#xff1a; &#xff08;1&#xff09;数组中的每个数据元素都是相同的数据类型。 &#xff08;2&#xff09;数组是有连续的内存空间组成的。 2、一维数组 2.1维数组定…

WPF中的VisualState(视觉状态)

以前在设置控件样式或自定义控件时&#xff0c;都是使用触发器来进行样式更改。触发器可以在属性值发生更改时启动操作。 像这样&#xff1a; <Style TargetType"ListBoxItem"><Setter Property"Opacity" Value"0.5" /><Setter …

day04【入门】MySQL学习(1)

目前的学习进度&#xff0c;如上图所示。从晚上开始学习MySQL数据库啦。 目录 1、数据库简介 2、数据集连接及准备工作 3、sql 语言中的注释 4、MySQL中常用数据类型 5、数据库中元素 6、创建表 7、insert插入记录 8、select查询 9、update修改数据 10、delete删除、t…