数据结构——图(二、图的存储和基本操作)

一、邻接矩阵

1、邻接矩阵定义

一个一维数组存储图中顶点的信息,一个二维数组存储图中边的信息(各顶点之间的邻接关系)。

邻接矩阵可用来存储无向图、有向图

若图中有n个顶点,则邻接矩阵A是nn

邻接矩阵采用顺序存储结构
空间复杂度为O(|V|²)

与边数无关

存储顶点信息使用O(|V|),存储边信息使用O(|V|²)

更多用于稠密图

1)无向图的邻接矩阵

注:对称矩阵且唯一,对称矩阵在实际存储时只需要存储上(下)三角矩阵中的元素

|----------------------------------------------------------------|---|---|---|---|---|---|---|
| 如右表黄色区域,有向图的邻接矩阵。 G[i][j]=0,两顶点间不存在边 G[i][j]=1,两顶点间存在边 | || 0 | 1 | 2 | 3 | 4 |
| 如右表黄色区域,有向图的邻接矩阵。 G[i][j]=0,两顶点间不存在边 G[i][j]=1,两顶点间存在边 | || A | B | C | D | E |
| 如右表黄色区域,有向图的邻接矩阵。 G[i][j]=0,两顶点间不存在边 G[i][j]=1,两顶点间存在边 | 0 | A | 0 | 1 | 1 | 1 | 0 |
| 如右表黄色区域,有向图的邻接矩阵。 G[i][j]=0,两顶点间不存在边 G[i][j]=1,两顶点间存在边 | 1 | B | 1 | 0 | 0 | 0 | 1 |
| 如右表黄色区域,有向图的邻接矩阵。 G[i][j]=0,两顶点间不存在边 G[i][j]=1,两顶点间存在边 | 2 | C | 1 | 0 | 0 | 1 | 0 |
| 如右表黄色区域,有向图的邻接矩阵。 G[i][j]=0,两顶点间不存在边 G[i][j]=1,两顶点间存在边 | 3 | D | 1 | 0 | 1 | 0 | 0 |
| 如右表黄色区域,有向图的邻接矩阵。 G[i][j]=0,两顶点间不存在边 G[i][j]=1,两顶点间存在边 | 4 | E | 0 | 1 | 0 | 0 | 0 |

对于无向矩阵

度遍历行或列

顶点A的度 = 邻接矩阵第0行(或第0列)的非零元素(或非∞元素)的个数之和

时间复杂度为O(|V|)

2)有向图的邻接矩阵

注:唯一,但是由于两个点之间的边是双向的,因此并不对称

|----------------------------------------------------------------------|---|---|---|---|---|---|---|
| 如右表黄色区域,有向图的邻接矩阵。 G[i][j]=0,顶点i到顶点j不存在弧 G[i][j]=1,顶点i到顶点j存在弧 | || 0 | 1 | 2 | 3 | 4 |
| 如右表黄色区域,有向图的邻接矩阵。 G[i][j]=0,顶点i到顶点j不存在弧 G[i][j]=1,顶点i到顶点j存在弧 | || A | B | C | D | E |
| 如右表黄色区域,有向图的邻接矩阵。 G[i][j]=0,顶点i到顶点j不存在弧 G[i][j]=1,顶点i到顶点j存在弧 | 0 | A | 0 | 1 | 1 | 1 | 0 |
| 如右表黄色区域,有向图的邻接矩阵。 G[i][j]=0,顶点i到顶点j不存在弧 G[i][j]=1,顶点i到顶点j存在弧 | 1 | B | 1 | 0 | 0 | 0 | 0 |
| 如右表黄色区域,有向图的邻接矩阵。 G[i][j]=0,顶点i到顶点j不存在弧 G[i][j]=1,顶点i到顶点j存在弧 | 2 | C | 0 | 0 | 0 | 1 | 0 |
| 如右表黄色区域,有向图的邻接矩阵。 G[i][j]=0,顶点i到顶点j不存在弧 G[i][j]=1,顶点i到顶点j存在弧 | 3 | D | 0 | 0 | 0 | 0 | 0 |
| 如右表黄色区域,有向图的邻接矩阵。 G[i][j]=0,顶点i到顶点j不存在弧 G[i][j]=1,顶点i到顶点j存在弧 | 4 | E | 0 | 1 | 0 | 0 | 0 |

对于有向矩阵

出度遍历行,入度遍历列

顶点A的度 = 出度+入度 = 邻接矩阵++第0行++ 的非零元素(或非∞元素)的个数 + 邻接矩阵++第0列++的非零元素 (或非∞元素)的个数 = 3+1 = 4

时间复杂度为O(|V|)+O(|V|)

3)带权图的邻接矩阵

无穷∞表示两个顶点之间不存在边(也可以使用0表示)

2、邻接矩阵性质

用邻接矩阵存储图

1、很容易确定图中任意两个顶点之间是否有边相连

比如无向图中,判断A与D之间是否相连,检查G[0][3]==1或G[3][0]==1

2、若要确定图中有多少条边,必须按行、按列对每个元素进行遍历,时间复杂度为O(|V|²)

3、若要确定顶点的度,必须按行、按列对每个元素进行遍历,时间复杂度为O(|V|)

3、邻接矩阵存储结构(算法实现)

cpp 复制代码
#define INFININITY //最大int值,即无穷
#define MAXVertexNum 100	//最大顶点数
 
typedef char VerTexType;	//顶点对应的数据类型 
typedef int ArcType;	//边对应的数据类型 

typedef struct{
	VerTexType vexs[MAXVertexNum];	//顶点表
	ArcType arcs[MAXVertexNum][MAXVertexNum];	//邻接矩阵,边表
	int vexnum,arcnum;	//图的当前点数和边数
}MGraph;

1、在简单应用中,可以直接使用二维数组作为图的邻接矩阵(顶点信息可忽略)

2、当邻接矩阵的元素仅表示相应边是否存在时,EdgeType可用值为0或1的布尔类型
邻接矩阵具体算法操作查看

图------邻接矩阵基本操作算法实现-CSDN博客

4、邻接矩阵的相关计算

[1][1] = 顶点B到顶点B的长度为2的路径的数目

=第2行*第2列

=

=2
[0][3] = 顶点A到顶点D的长度为2的路径的数目

=第1行*第4列

=

=1

二、邻接表

1、邻接表定义

对图中每个顶点建立一个单链表,第i个单链表中的结点表示依附于顶点i的边(对于有向图则是以顶点i为尾的弧),称为边表(对于有向图则是出边表)

边表的头指针和顶点的数据信息采用顺序存储,即顶点表

结合顺序存储和链式存储

若G为无向图,则所需存储空间为O(|V|)+O(2|E|),因为每条边在邻接表出现两次

若G为有向图,则所需存储空间为O(|V|)+O(|E|)

2、邻接表的存储结构(算法实现)

cpp 复制代码
#define MAXVertexNum 100	//最大顶点数

typedef struct ArcNode{    //边表结点 
 	int adjvex;  //该边所指向的顶点的位置 
 	struct ArcNode *nextarc;//指向下一条边的指针 
 	Otherinfo info;  //网的边权值 
}ArcNode;

typedef struct VNode{    //顶点结点
 	VerTexType data;  //顶点信息
 	ArcNode *firstarc;   //指向第一条依附该顶点的边的指针 
}VNode,AdjList[MAXVertexNum];//AdjList表示邻接表类型

typedef struct{    
 	AdjList vertices;    //邻接表
 	int vexnum,arcnum;    //图的当前顶点数和弧数
}ALGraph;

关于邻接表更多具体基础操作算法查看附件

图------邻接表基本操作算法实现-CSDN博客

3、邻接表性质

1、对于稀疏表(边数较少的表),采用邻接表将极大节省空间

2、图的邻接表不唯一,在每个顶点对应的边表中,各边结点的链接次序可以是任意的,取决于建立邻接表的算法及边的输入次序。
1、给定一个顶点,极其容易找出它的所有邻边(对于有向图,则是出边)

遍历该顶点的单链表,最差时间复杂度为O(|V|)

2、对于有向图,找一个顶点A的入边,需要遍历所有顶点(除该顶点A外)的边表,找到结点A,时间复杂度为O(|E|)

3、若要确定两个顶点间是否存在边,找到相应结点对应的边表中查找另一个结点

对于有向图,如果出边未找到,还需要遍历所有顶点(除自身)的边表,找到自身结点。

4、在有向图的邻接表中,求某个顶点的出度只需要计算其邻接表中的边表个数,但求某个顶点x的入度则需要遍历全部邻接表,统计邻接点域为x的边表结点个数。

三、十字链表

是有向图的一种链式存储结构

有向图的每条弧用一个结点(弧节点)表示,每个顶点用一个(顶点结点)表示

解决入边难寻的问题,同时节省存储空间,空间复杂度为O(|V|+|E|)

|------|----------------|----------------|
| data | firstin | firstout |
| 数据信息 | 指向以该顶点为弧头的第一条弧 | 指向以该顶点为弧尾的第一条弧 |
[顶点结点]

|--------|---------|-------------|-------------|--------|
| tailex | headvex | hlink | tlink | (info) |
| 弧尾编号 | 弧头编号 | 指向弧头相同的下一条弧 | 指向弧尾相同的下一条弧 | 弧的相关信息 |
[弧结点]

顶点结点之间是顺序存储,弧结点省略了info域

图的十字链表不是唯一的,但是一个十字链表表示唯一确定的一个图

在十字链表中,既容易找到以V为尾的弧,也容易找到以V为头的弧,因而容易求得顶点的出度和入度

四、邻接多重表

邻接多重表是无向图的一种链式存储

解决邻接表中求两个顶点之间是否存在边而执行删除边等操作时,需要分别遍历两个顶点的边表的问题。

|------|----------------|---------|----------------|--------|
| ivex | ilink | jvex | tlink | (info) |
| 顶点编号 | 依附于顶点ivex的下一条边 | 另一个顶点编号 | 依附于顶点jvex的下一条边 | 弧的相关信息 |
[边表结点]

|------|-------------|
| data | firstedge |
| 数据信息 | 依附于该顶点的第一条边 |
[顶点结点]

在邻接多重表中,所有依附于同一顶点的边串联在同一链表中

因为每条边依附于两个顶点,所以每个边结点同时链接在两个链表中
邻接多重表和邻接表的区别

同一条便在邻接表中用两个结点表示,在邻接多重表中只有一个结点

五、图的基本操作

Adjacent(G,x,y) 判断图是否存在边<x,y>或(x,y)

Neighbors(G,x) 列出图中与结点x邻接的边

InsertVertex(G,x) 在图中插入顶点x

DeleteVertex(G,x) 在图中删除顶点x

AddEdge(G,x,y) 若无向边(x,y)或有向边<x,y>不存在,则向图G中添加该边

RemoveEdge(G,x,y) 若无向边(x,y)或有向边<x,y>存在,则从图G中删除该边

FirstNeighbor(G,x) 求图中顶点x的第一个邻接点,若有则返回定点好,若x没有邻接点或图中不存在x,则返回-1

NextNeighbor(G,x) 假设图中顶点y是顶点x的一个邻接点,返回除y外顶点x的下一个邻接点的顶点号,若y是x的最后一个邻接点,则返回-1

Get_edge_value(G,x,y) 获取图中边(x,y) 或 <x,y>对应的权值

Set_edge_value(G,x,y,v) 设置图中边(x,y) 或 <x,y>对应的权值为v

|---------------------------------------|-----------|-----------|-----------------------------------------------------------------------------------------------------|-------------------------------------|
| V表示顶点数、E表示边数,此表显示最差所需消耗时间,时间复杂度取最高量级即可 这里假设顶点为A、B、C,因此,需要知道索引需要遍历顶点集操作,时间复杂度为O(|V|),下表中均省略这一步的时间复杂度 |||||
| 操作 | 邻接矩阵 || 邻接表 ||
| 操作 | 有向图 | 无向图 | 有向图 | 无向图 |
| InsertVertex(G,x) 插入顶点 | O(1) | O(1) | O(1) | O(1) |
| DeleteVertex(G,x) 删除顶点 | O(|V|²) | O(|V|²) | O(|V| + |E|) | O(|V| + 2|E|) |
| DeleteVertex(G,x) 删除顶点 | 找到顶点索引,删除顶点所在行列 || 找到顶点索引,删除顶点链接的所有边表结点 遍历所有边表结点,删除该顶点的边表结点 ||
| AddEdge(G,x,y) 插入边 | O(1) | O(1) | O(1) | O(2) |
| AddEdge(G,x,y) 插入边 | 找到两个顶点的索引,设置 G.arcs[i][j](G.arcs[i][j]与G.arcs[j][i]) || 找到两个顶点的索引, 在顶点表找到弧头, 在该顶点表结点的边表头指针后添加边表结点 | 找到两个顶点的索引, 分别在这两个顶点表结点的边表头指针后添加边表结点 |
| RemoveEdge(G,x,y) 删除边 | O(1) | O(1) | O(|V|) | O(2|V|) |
| RemoveEdge(G,x,y) 删除边 | 找到两个顶点的索引,设置 G.arcs[i][j](G.arcs[i][j]与G.arcs[j][i]) || 找到边的弧头索引, 遍历该弧头的边表结点 | 找到两个顶点的索引, 分别遍历两个顶点的边表结点 |
| Adjacent(G,x,y) 判断图是否存在边<x,y>或(x,y) | O(1) | O(1) | O(|V|) | O(|V|) |
| Adjacent(G,x,y) 判断图是否存在边<x,y>或(x,y) | 找到两个顶点的索引,判断 G.arcs[i][j](或G.arcs[j][i]) || 在顶点表找到弧头, 遍历该弧头的边表结点 | 找到某个顶点的索引, 遍历顶点的边表结点 |
| Neighbors(G,x) 列出图中结点x的邻接的边 | O(|V|) | O(|V|) | O(|V| + |E|) | O(|V|) |
| Neighbors(G,x) 列出图中结点x的邻接的边 | 找到结点x的索引,遍历结点x所在的行(或行列) || 在顶点表找到弧头,遍历该弧头的边表结点(出边) 遍历整个边表节点找弧头(入边) | 找到某个顶点的索引, 遍历顶点的边表结点 |
| FirstNeighbor(G,x) 获取第一个邻接点 | O(|V|) | O(|V|) | O(|E|) | O(1) |
| FirstNeighbor(G,x) 获取第一个邻接点 | 找到结点x的索引,遍历结点x所在的行(或行列) || 找到结点x的索引,遍历结点x的第一个边表结点如果为空,遍历所有边表结点 | 找到结点x的索引, 遍历结点x的第一个边表结点 如果为空,则不存在 |
| NextNeighbor(G,x,y) 获取下一个邻接点 | O(|V|) | O(|V|) | O(|V|)+O(|E|) | O(|V|) |
| NextNeighbor(G,x,y) 获取下一个邻接点 | 找到结点x的索引,遍历结点x所在的行(或行列) || 在顶点表找到结点x,遍历结点x的边表结点,找到y的下一个结点 如果结点x的边表结点不存在,则在顶点表结点找到结点y,遍历顶点表结点y之后的所有顶点表结点的边表结点,如果找到结点x,则返回对应顶点结点 | 在顶点表找到结点x,遍历结点x的边表结点,找到y的下一个结点 |
| Get_edge_value(G,x,y) 获取边权值 | O(1) | O(1) | O(|V|) | O(|V|) |
| Set_edge_value(G,x,y,v) 设置边权值 | O(1) | O(1) | O(|V|) | O(2|V|) |

图的遍历算法见附件

数据结构------图(三、图的 广度/深度 优先搜索)-CSDN博客

六、图的四种存储方式总结对比

项目 邻接矩阵 邻接表 十字链表 邻接多重表
空间复杂度 O(|V|²) 无向图:O(|V| + 2|E|) 有向图:O(|V| + |E|) O(|V| + |E|) O(|V| + |E|)
找相邻边 遍历对应行或列的时间 复杂度为O(|V|) 找有向图的入度必须遍历整个邻接表 很方便 很方便
删除边或顶点 删除边很方便,删除顶点需大量移动数据 无向图中删除边或顶点都不方便 很方便 很方便
适用于 稠密图 稀疏图和其他 只能存有向图 只能存无向图
表示方式 唯一 不唯一 不唯一 不唯一
相关推荐
云手机掌柜27 分钟前
从0到500账号管理:亚矩阵云手机多开组队与虚拟定位实战指南
数据结构·线性代数·网络安全·容器·智能手机·矩阵·云计算
Eloudy3 小时前
简明量子态密度矩阵理论知识点总结
算法·量子力学
点云SLAM3 小时前
Eigen 中矩阵的拼接(Concatenation)与 分块(Block Access)操作使用详解和示例演示
人工智能·线性代数·算法·矩阵·eigen数学工具库·矩阵分块操作·矩阵拼接操作
没书读了3 小时前
考研复习-数据结构-第八章-排序
数据结构
算法_小学生4 小时前
支持向量机(SVM)完整解析:原理 + 推导 + 核方法 + 实战
算法·机器学习·支持向量机
waveee1235 小时前
学习嵌入式的第三十四天-数据结构-(2025.7.29)数据库
数据结构·数据库·学习
iamlujingtao5 小时前
js多边形算法:获取多边形中心点,且必定在多边形内部
javascript·算法
算法_小学生5 小时前
逻辑回归(Logistic Regression)详解:从原理到实战一站式掌握
算法·机器学习·逻辑回归
DebugKitty6 小时前
C语言14-指针4-二维数组传参、指针数组传参、viod*指针
c语言·开发语言·算法·指针传参·void指针·数组指针传参
qystca6 小时前
MC0241防火墙
算法