高阶数据结构[2]图的初相识

news2024/11/25 4:21:32

图的初相识

1.前言

2.图的概念

3.图的相关术语

4.图的存储结构 

4.1邻接矩阵 

4.2邻接表 

4.3两种存储方式的对比

 5.图的存储实现

5.1邻接矩阵的实现

5.2邻接表的实现 

6.总结 


1.前言

本章将大家学习数据结构中的“图”。有学习过离散数学的同学对这一章节或许会比较熟悉。本篇文章将从最基础的概念开始介绍,一步步深入。让我们开始吧!

2.图的概念

大家是否还记得我们学过的“树”?“树”就是一种无环的连通图。

图有两个部分组成:1.顶点集合  2.顶点间形成的边。

因此图是一种由顶点集合以及边集合组成的一种数据结构表示为G=(V,E)。

下面我们用图像来理解该概念。

如上图所示。G1中四个顶点0,1,2,3 ,各顶点形成连接的边。抽象概念可以表示为顶点集合v1,v2,v3,v4。形成的边可以表示为ek=(vi,vj),表示改边由顶点vi和vj连接而成。

G2就是我们刚刚介绍的树是一种无向连通图。

 既然说到了无向图,那么必然就有有向图。如图,G3,G4均为有向图。相信大家能看出来有向图和无向图的区别了!无向图没有明确的指向,而有向图则有明确的指向。

3.图的相关术语

在图的学习中,会涉及到大量的专业名词,我们在此一一列举。

\bigstar完全图:在有n个顶点的无向图中,若有n * (n-1)/2条边,即任意两个顶点之间有且仅有一条边,则称此图为无向完全图. 上图的G1就是无向完全图, G4为有向完全图。


\bigstar邻接顶点: 若顶点u和v有直接的边相连, 那么它们这两个顶点就称为邻接顶点。


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

上图中的G3中顶点1的出度为2,入度为1,顶点的度为3.


\bigstar路径:若顶点A可以到达顶点B, 则从A到B经过的所有顶点就是A到B的路径. 对于不带权图,路径长度等于边数之和,带权图则是权值之和。如图。


 \bigstar简单路径和回路:从图上的任一顶点出发,路径上经过的顶点只过一次,则为简单路径。

回路则是路径的上的出发点和结束点为同一个顶点形成一个环。如图。


 \bigstar子图:图2的顶点和边都是图1的一部分 ,则称图2是图1的子图。如图。


 \bigstar连通图:在无向图中,若两顶点间有边则称这两个边连通。若任意两个顶点都有边,则称为连通图。


\bigstar强连通图:在有向图中,任意两个顶点间都有相连的边,并且顶点1有指向顶点2,顶点2也指向1。


\bigstar生成树:一个无向连通图的最小连通子图称为改图的生成树。n个顶点的连通图的生成树有n-1条边。

4.图的存储结构 

图的存储结构有两种:1.邻接矩阵 2.邻接表

4.1邻接矩阵 

 邻接矩阵用以判断顶点间是否连通,用0来表示连通,1来表示不连通。

因此,我们用二维数组来实现邻接矩阵,表示两点是否连接。

 如上图,对于无向图,矩阵是对称的。因为,A对于B是连通的,B对于A也是连通的。

对于有向图而言,如图,二维数组的行表示边是由该顶点出发,而列则为被指向的顶点。

若图中的边带有权值,则在二维矩阵中用无穷大来表示两顶点间无边即不连通。

4.2邻接表 

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

无向图的邻接表

 假设顶点A,B,C,D 的下标为0,1,2,3。与A连通的有B,C。所以在邻接表中,A指向B,C。

有向图的邻接矩阵 

有向图的邻接表中包含有入边表和出边表。以A为例,顶点E指向A因此在入边表中A连接E的下标4。在出边表中,由于从A出的边指向B和D,所以出边表指向B和D的下标1,3。 

 4.3两种存储方式的对比

而邻接表的优点是很快能判断出一个顶点与哪些顶点直接相连. 而邻接表想要知道两个顶点是否连通,要比邻接矩阵要麻烦。

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

 5.图的存储实现

 首先,和学习其他数据结构一样。我们先来实现图的基本框架。上代码。

template<class V,class W,W MAX_W=INT_MAX,bool Direction=false>//V表示顶点的类型 W表示边的类型,MAX_W作为二维数组的初始值
class Graph {
piblic:
	Graph(const V* a, size_t n)
	{
		_vertrx.resize(n);//为顶点集合开辟空间
		for (int i = 0; i < n; i++)
		{
			_vertex.push(a[i]);//将数组中的值写入集合
			_indexMap[a[i]] = i;//顶点值和顶点下标的映射
		}
		_edge.resize(n);//为边的集合开辟空间
		for (int i = 0; i < n; i++)
		{
			_edge[i].resize(n, MAX_W);
		}
	}
private:
	vector<v> _vertex;//存储顶点的集合
	vector<vector<w>>edge;//存储边的集合
	map<v, w>_indexMap;//存储顶点和其映射
};

5.1邻接矩阵的实现

	namespace martix {
		template<class V, class W, W MAX_W = INT_MAX, bool Direction = false>
		class Graph {
		public:
			Graph(const V* a, size_t n)
			{
				_vertex.reserve(n);
				for (int i = 0; i < n; i++)
				{
					_vertex.push_back(a[i]);
					_indeMap[a[i]] = i;
				}
				_edge.resize(n, MAX_W);
			}
			size_t GetIndex(const V& v)
			{
				if (_indexMap.find(v) == _index.end())//map的特性,找不到时就等于end
				{
					cout << "要添加的顶点不存在" << endl;
					return -1;
				}
				return _index[v];
			}
			void AddEdge(const V& src, const V& dest, const W& w)//加边,依次为源点,目标点和边的权值
			{
				size_t srci = GetIndex(src);
				size_t desti = GetIndex(dest);
				_edge[srci][desti] = w;//边的权值
				if (Direction = false)
				{
					_edge[desti][srci] = w;//无向图,矩阵是对称的
				}
				void print()
				{
					//打印顶点
					for (int i = 0; i < _edge.size(); i++)
						cout << "[" << i << "]" << "->" << _vertex[i] << endl;
					cout << endl;
					//打印矩阵
					for (int i = 0; i < _edge.size(); i++)//横坐标
					{
						for (int j = 0; i < _edge[i]).size(); j++)//纵坐标
						{
						if (_edge[i][j] = MAX_W)
							cout << "*";
						else {
							cout << _edge[i][j] << " ";
						}
}
					}
					cout << endl;
				}
				cout << endl;
			}
		};
	private:
		vector<V>_vertex;//顶点的集合
		vector<vector<w>> _edge;//边的集合
		map<v, w>_indexMap;
	}

5.2邻接表的实现 

	namespace link_table
	{
		template<class W>//边的权值
		struct Edge {
			int _dsti;//目标点的下标
			W _w;
			Edge<W>* _next;
				Edge(int dsti, constW& w)//邻接表用链表的形式实现,因此是对每一个结点进行初始化
				:_dsti(dsti)
				, _w(W)
				, _next(nullptr)
			{}
		};
		template<class V, class W, bool Direction = false>
		class Graph {
			typedef Edge<W> Edge;
		public:
			Graph(const V* a, size_t n)
			{
				_vertex.reverse(n);//顶点集合开辟空间
				for (int i = 0; i < n; i++)
				{
					_vertex.push_back(a[i]);
					_indexMap(a[i]) = i;
				}
				_tables.resize(m, nullptr);
			}
			size_t GetVertexIndex(const V& v)
			{
				size_t it = _indexMap.find(v);
				if (it != _indexMap.end())
				{
					return it->second;//返回顶点集合映射的下标
				}
				else {
					return -1;
				}
			}
			void AddEdge(const V& src, const V& dst, const W& w)
			{
				size_t srci = GetVertexIndex(src);
				sizet_t dsti = GetVertexIndex(dst);
				Edge* eg = new Edge(dsti, w);//对结点进行开辟
				eg->_next = _tables[srci];//头插的方式加入邻接表的新边,指向原来的头
				_tables[srci] = eg;//做新头
				if (Direction == false)
				{
					Edge* eg = new Edge(srci, w);
					eg->_next;_tables[dsti];
					_tables[dsti] = edge;//做新头
				}
			}
			void Print()
			{
				for (size_t i = 0; i < _vertex.size(); i++)
				{
					cout << "[" << i << "]" << "->" << _vertex[i] << endl;//下标对应的顶点
				}
				cout << endl;
				for (size_t i = 0; i < _tables.size(); i++)
				{
					cout << _vertex[i] << "[" << i << "]" << "->";
					Edge* cur = _tables[i];
					while (cur)//和链表一样的遍历方式
					{
						cout << "[" << _vertexs[cur->_dsti] << ":" << cur->_dsti << ":" << cur->_w << "]->";
						cur = cur->_next;
					}
				}
			}
		private:
			vector<V>_vertex;//顶点的集合
			map<V, int>_indexMap;
			vector<Edge*>_tables;
		};
	
	}

可以看到,邻接矩阵和邻接表在实现时的差别,邻接矩阵使用二维数组,而邻接表则使用的链表。

此外,在邻接表中插入新边时,我们使用的是头插的方式,因为这样可以免去找链表尾的操作。 

6.总结 

本篇文章,我们对图进行了基本的了解。在后续讲解图的相关算法时,我们将以邻接矩阵的存储方式的基本框架来分析。

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

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

相关文章

和鲸科技执行总裁殷自强:面向空间数据协同分析场景的模型生命周期管理方法

导读&#xff1a; 由 ACM SIGSPATIAL 中国分会主办的第五届空间数据智能学术会议&#xff08;SpatialDI 2024&#xff09;于 2024 年 4 月 25 日- 27 日在南京圆满召开&#xff0c;主题为“ AGI 时代下的空间数据智能”&#xff0c;旨在深入推动空间数据智能研究的理论进步与应…

os实训课程模拟考试(8~13)

基于信号量的进程间通信 信号量IPC操作考查 编程要求 根据提示&#xff0c;在右侧编辑器补充代码&#xff0c;了解OpenEuler系统如何使用信号量进行IPC通信&#xff0c;代码中先用sem_read_array[]数组存储数据&#xff0c;并进行信号量与数据的输出&#xff0c;我们需要补充…

大数据与人工智能在保险行业数字化转型中的应用

随着科技的快速发展&#xff0c;大数据和人工智能&#xff08;AI&#xff09;技术在保险行业中扮演着越来越重要的角色&#xff0c;推动了保险行业的数字化转型。通过收集和分析海量的用户数据&#xff0c;利用先进的人工智能算法&#xff0c;保险公司能够更准确地评估风险&…

消息队列-概述-JMS和AMQP

JMS和AMQP JMS是什么 JMS&#xff08;JAVA Message Service,java 消息服务&#xff09;是 Java 的消息服务&#xff0c;JMS 的客户端之间可以通过 JMS 服务进行异步的消息传输。JMS&#xff08;JAVA Message Service&#xff0c;Java 消息服务&#xff09;API 是一个消息服务…

docker desktop for mac os如何使用本地代理

在macbook上弄了个代理&#xff0c;然后按照网上所说的去配代理 然后测试下 docker pull busybox 结果无反应&#xff0c;超时。我去&#xff01;&#xff01;&#xff01; 鼓捣了半天&#xff0c;看了docker官网&#xff0c;问了chatgpt &#xff0c;按照它们所说的试了下也没…

IDEA导入项目报错java程序包不存在

如图文件结构&#xff0c;本来是在web-demo中操作&#xff0c;但是想导入一下其他模块&#xff0c;切换了项目文件的目录&#xff0c;发现需要重新对Tomcat等进行配置&#xff0c;配置好之后发现运行出现Java相关错误&#xff08;如下&#xff09;记录一下修正过程。 java: 程序…

中国最著名的起名大师颜廷利:父亲节与之相关的真实含义

今天是2024年6月16日&#xff0c;这一天被广泛庆祝为“父亲节”。在汉语中&#xff0c;“父亲”这一角色常以“爸爸”、“大大”&#xff08;da-da&#xff09;或“爹爹”等词汇表达。有趣的是&#xff0c;“爸爸”在汉语拼音中表示为“ba-ba”&#xff0c;而当我们稍微改变“b…

消息队列-概述-什么是消息队列

什么是消息队列 我们可以把消息队列看作是一个存放消息的容器&#xff0c;当我们需要使用消息的时候&#xff0c;直接从容器中取出消息供自己使用即可。由于队列 Queue 是一种先进先出的数据结构&#xff0c;所以消费消息时也是按照顺序来消费的。 参与消息传递的双方称为 生产…

c++20 规范, vs2019 , 头文件 <mutex> ,注释以及几个探讨

&#xff08;1 探讨一&#xff09; mutex 这个名称的来源是 mutual exclusion &#xff1a;互相排斥。 mutex 与 recursive_mutex 的数据成员的定义如下&#xff1a; 测试如下&#xff1a; 运行以下&#xff1a; 以及&#xff1a; &#xff08;2 探讨二&#xff09; recursive_…

Orange Pi AIpro:高性能AI开发板开箱体验及样例测试

文章目录 前言背景介绍产品介绍主要参数配置AI处理器——昇腾310 NPU模型训练预测加载resnet50模型真实动物测试虚拟动物测试 前言 随着人工智能和物联网技术的迅速发展&#xff0c;单板计算机&#xff08;Single Board Computer, SBC&#xff09;在创客和开发者社区中越来越受…

buuctf-findKey

exe文件 运行发现这个窗口,没有任何消息 32位 进入字符串就发现了flag{ 左边红色代表没有F5成功 我们再编译一下(选中红色的全部按p) LRESULT __stdcall sub_401640(HWND hWndParent, UINT Msg, WPARAM wParam, LPARAM lParam) {int v5; // eaxsize_t v6; // eaxDWORD v7; /…

1055 集体照(测试点3, 4, 5)

solution 从后排开始输出&#xff0c;可以先把所有的学生进行排序&#xff08;身高降序&#xff0c;名字升序&#xff09;&#xff0c;再按照每排的人数找到中间位置依次左右各一个进行排列测试点3&#xff0c; 4&#xff0c; 5&#xff1a;k是小于10的正整数&#xff0c;则每…

Spring5中IOC创建对象的方式(有参与无参)与时机(附三类无参创建代码供参考)

Spring5中IOC创建对象的方式(有参与无参)附三类无参创建代码供参考 1. IOC容器 IOC是Spring框架的核心内容&#xff0c;Spring容器使用多种方式完美的实现了IOC&#xff0c;可以使用XML配置&#xff0c;也可以使用注解&#xff0c;新版本的Spring也可以零配置实现IOC。 Spri…

嵌入式微处理器重点学习(三)

堆栈操作 R1=0x005 R3=0x004 SP=0x80014 STMFD sp!, {r1, r3} 指令STMFD sp!, {r1, r3}是一条ARM架构中的存储多个寄存器到内存的指令,这里用于将r1和r3寄存器的内容存储到栈上。STMFD(Store Multiple Full Descending)是一种全递减模式的多寄存器存储指令,它会先将栈指针…

流媒体传输协议HTTP-FLV、WebSocket-FLV、HTTP-TS 和 WebSocket-TS的详细介绍、应用场景及对比

一、前言 HTTP-FLV、WS-FLV、HTTP-TS 和 WS-TS 是针对 FLV 和 TS 格式视频流的不同传输方式。它们通过不同的协议实现视频流的传输&#xff0c;以满足不同的应用场景和需求。接下来我们对这些流媒体传输协议进行剖析。 二、传输协议 1、HTTP-FLV 介绍&#xff1a;基于 HTTP…

MySQL-创建表~数据类型

070-创建表 create table t_user(no int,name varchar(20),gender char(1) default 男);071-插入数据 语法格式&#xff1a; insert into 表名(字段名1, 字段名2, 字段名3,......) values (值1,值2,值3,......);insert into t_user(no, name, gender) values(1, Cupid, 男);字…

嵌入式门槛高不高,工资怎么样?

一般来说&#xff0c;嵌入式岗位的准入门槛其实并不是特别高。通常情况下&#xff0c;只要能够熟练掌握 C 语言编程以及单片机相关知识&#xff0c;就能够去制作一些较为简单的电子产品&#xff0c;由此可见其门槛相对而言是比较低的&#xff0c;相应的薪水可能也不会特别高。 …

【Kafka】Kafka提高生产者吞吐量、数据可靠性-06

【Kafka】Kafka提高生产者吞吐量-06 1. 提高生产者吞吐量2.数据可靠性2.1 回顾数据的发送流程2.2 ack应答级别2.2.1 acks:02.2.2 acks:12.2.2 acks:-1(all)2.2.2.1 数据可靠性分析2.2.2.2 数据完全可靠 2.3 可靠性总结2.4 可靠性代码配置 1. 提高生产者吞吐量 import org.apach…

[C++] vector list 等容器的迭代器失效问题

标题&#xff1a;[C] 容器的迭代器失效问题 水墨不写bug 正文开始&#xff1a; 什么是迭代器&#xff1f; 迭代器是STL提供的六大组件之一&#xff0c;它允许我们访问容器&#xff08;如vector、list、set等&#xff09;中的元素&#xff0c;同时提供一个遍历容器的方法。然而…

Vue26-内置指令03:v-cloak指令

一、需求 将引入本地JS的代码&#xff0c;换成引入外部JS&#xff0c;且引入的外部JS要等待5S。 【备注】&#xff1a;浏览器也能调节网速 二、js阻塞 <body>的最下方也能引入JS&#xff1a; 此时&#xff0c;用户能在5S内看到root容器未编译的部分。 解决该问题&#x…