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

发布于:2025-07-29 ⋅ 阅读:(27) ⋅ 点赞:(0)

一、邻接矩阵

1、邻接矩阵定义

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

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

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

邻接矩阵采用顺序存储结构

空间复杂度为O(|V|²)

与边数无关

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

更多用于稠密图

1)无向图的邻接矩阵

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

如右表黄色区域,有向图的邻接矩阵。

G[i][j]=0,两顶点间不存在边

G[i][j]=1,两顶点间存在边

0 1 2 3 4
A B C D E
0 A 0 1 1 1 0
1 B 1 0 0 0 1
2 C 1 0 0 1 0
3 D 1 0 1 0 0
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
A B C D E
0 A 0 1 1 1 0
1 B 1 0 0 0 0
2 C 0 0 0 1 0
3 D 0 0 0 0 0
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、邻接矩阵存储结构(算法实现)

#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、邻接矩阵的相关计算

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

=第2行*第2列

=a_{10}a_{01}+a_{11}a_{11}+a_{12}a_{21}+a_{13}a_{31}+a_{14}a_{41}

=2

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

=第1行*第4列

=a_{00}a_{03}+a_{01}a_{13}+a_{02}a_{23}+a_{03}a_{33}+a_{04}a_{43}

=1

二、邻接表

1、邻接表定义

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

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

结合顺序存储和链式存储

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

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

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

#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|)
找到顶点索引,删除顶点所在行列

找到顶点索引,删除顶点链接的所有边表结点

遍历所有边表结点,删除该顶点的边表结点

AddEdge(G,x,y)
插入边

O(1) O(1) O(1) O(2)
找到两个顶点的索引,设置 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|)
找到两个顶点的索引,设置 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|)
找到两个顶点的索引,判断 G.arcs[i][j](或G.arcs[j][i])

在顶点表找到弧头,

遍历该弧头的边表结点

找到某个顶点的索引,

遍历顶点的边表结点

Neighbors(G,x)

列出图中结点x的邻接的边

O(|V|) O(|V|) O(|V| + |E|) O(|V|)
找到结点x的索引,遍历结点x所在的行(或行列)

在顶点表找到弧头,遍历该弧头的边表结点(出边)

遍历整个边表节点找弧头(入边)

找到某个顶点的索引,

遍历顶点的边表结点

FirstNeighbor(G,x)

获取第一个邻接点

O(|V|) O(|V|) O(|E|) O(1)
找到结点x的索引,遍历结点x所在的行(或行列)

找到结点x的索引,

遍历结点x的第一个边表结点

如果为空,遍历所有边表结点

找到结点x的索引,

遍历结点x的第一个边表结点

如果为空,则不存在

NextNeighbor(G,x,y)

获取下一个邻接点

O(|V|) O(|V|) O(|V|)+O(|E|) O(|V|)
找到结点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|)
找有向图的入度必须遍历整个邻接表 很方便 很方便
删除边或顶点 删除边很方便,删除顶点需大量移动数据 无向图中删除边或顶点都不方便 很方便 很方便
适用于 稠密图 稀疏图和其他 只能存有向图 只能存无向图
表示方式 唯一 不唯一 不唯一 不唯一


网站公告

今日签到

点亮在社区的每一天
去签到