图的四种存储方式(邻接矩阵、邻接表、十字链表、邻接多重表)


图的存储必须要完整、准确地反应顶点集和边集的信息。根据不同图的结构和算法,采用不同的存储方式将对程序的效率产生相当大的影响,因此所选的存储结构应适合于待求解的问题。下面介绍四种图的存储方式。

一、邻接矩阵法

1. 概念

邻接矩阵存储是指用一个一维数组存储图中顶点的信息,用一个二维数组存储图中边的信息(即各顶点之间的邻接关系),存储顶点之间邻接关系的二维数组称为邻接矩阵。
【在简单应用中,可直接用二维数组作为图的邻接矩阵(顶点信息等均可忽略)】

顶点数为 n 的图 G = (V, E) 的邻接矩阵 A 是 n × n 的,将 G 的顶点编号为 v1, v2,…, vn ,则:

对于带权图而言,若顶点 vi 和 vj 之间有边相连,则邻接矩阵中对应项存放着该边对应的权值,若顶点 vi 和 vj 不相连,则通常用 0 或 ∞ 来代表这两个顶点之间不存在边:

有向图、无向图和网对应的邻接矩阵示例如下图所示:

2. 图的邻接矩阵存储结构定义

#define MaxVertexNum 100//顶点数目的最大值
typedef char VertexType;//顶点对应的数据类型
typedef int EdgeType;//边对应的数据类型
typedef struct{
    VertexType vex[MaxVertexNum];//顶点表
    EdgeType edge[MaxVertexNum][MaxVertexNum];//邻接矩阵(边表)
    int vexnum, arcnum;//图当前的顶点数和边数
}MGraph;

【注】邻接矩阵表示法的空间复杂度为 O(n2) ,其中 n 为图的顶点数 |V| 。

3. 图的邻接矩阵存储表示法的特点

  • 对于有 n 个顶点的无向图,有 A[i][i] = 0 ,其中 1 ≤ i ≤ n 。

  • 无向图的邻接矩阵一定是一个对称矩阵(且唯一),即 A[i][j] = A[j][i](1 ≤ i ≤ n,1 ≤ j ≤ n),因此,在实际存储邻接矩阵时只需存储上(或下)三角矩阵的元素(压缩存储)。

  • 对于无向图,邻接矩阵的第 i 行(或第 i 列)非零元素(或非 ∞ 元素)的个数正好是顶点 i 的度 TD(vi) 。

  • 对于有向图,邻接矩阵的第 i 行非零元素(或非 ∞ 元素)的个数正好是顶点 i 的出度 OD(vi) ;邻接矩阵的第 i 列非零元素(或非 ∞ 元素)的个数正好是顶点 i 的入度 ID(vi) 。

  • 用邻接矩阵存储图,很容易确定图中任意两个顶点之间是否有边相连,但是,要确定图中有多少条边,则必须按行、按列对每个元素进行检测,所花费的时间代价很大。

  • 稠密图(即边数较多的图)适合采用邻接矩阵的存储方式。

  • (了解即可)设图 G 的邻接矩阵为 A ,Am 的元素 Am [i][j] 等于由顶点 i 到顶点 j 的长度为 m 的路径的数目。

4. 例题

① 若图的邻接矩阵中主对角线上的元素皆为 0 ,其余元素全为 1 ,则可以断定该图一定( C )。
A. 是无向图
B. 是有向图
C. 是完全图
D. 不是带权图

② 在含有 n 个顶点和 e 条边的无向图的邻接矩阵中,零元素的个数为( D ) 。
A. e
B. 2e
C. n2 - e
D. n2 - 2e

③ 带权有向图 G 用邻接矩阵存储,则 vi 的入度等于邻接矩阵中( D ) 。
A. 第 i 行非 ∞ 的元素个数
B. 第 i 列非 ∞ 的元素个数
C. 第 i 行非 ∞ 且非 0 的元素个数
D. 第 i 列非 ∞ 且非 0 的元素个数

④ 一个有 n 个顶点的图用邻接矩阵 A 表示,若图为有向图,顶点 vi 的入度是( B );若图为无向图,顶点 vi 的度是( D )。
A. Σi=1n A[i][j]
B. Σj=1n A[j][i]
C. Σi=1n A[j][i]
D. Σj=1n A[j][i] 或 Σj=1n A[i][j]

⑤ 【2013 统考真题】设图的邻接矩阵 A 如下图所示。各顶点的度依次是( C )。


A. 1, 2, 1, 2
B. 2, 2, 1, 1
C. 3, 4, 2, 3
D. 4, 4, 2, 2

二、邻接表法

1. 概念

当一个图为稀疏图时,使用邻接矩阵法会浪费大量的存储空间,而图的邻接表法结合了顺序存储和链式存储的方法,大大减少了这种不必要的浪费。

邻接链表是指对图 G 中的每个顶点 vi 建立一个单链表,第 i 个单链表中的结点表示依附于顶点 vi 的边(对于有向图则是以顶点 vi 为尾的弧),这个单链表就称为顶点 vi边表(对于有向图则称为出边表)。边表的头指针和顶点的数据信息采用顺序存储,称为顶点表,所以在邻接表中存在两种结点:顶点表结点和边表结点,如下图所示:

顶点表结点由两个域组成:

  • 顶点域(data)存储顶点 vi 的相关信息;
  • 边表头指针域(firstarc)指向第一条边的边表结点。

边表结点至少由两个域组成:

  • 邻接表域(adjvex)存储与头结点顶点 vi 邻接的顶点编号;
  • 指针域(nextarc)指向下一条边的边表结点。

无向图、有向图对应的邻接表示例如下图所示:


2. 图的邻接表存储结构定义

#define MaxVertexNum 100//图中顶点数目的最大值
typedef struct ArcNode{//边表结点
    int adjvex;//该弧所指向的顶点的位置
    struct ArcNode *nextarc;//指向下一条弧的指针
    //InfoType info;//网的边权值
}ArcNode;
typedef struct VNode{//顶点表结点
    VertexType data;//顶点信息
    ArcNode *firstarc;//指向第一条依附该顶点的弧的指针
}VNode,AdjList[MaxVertexNum];
typedef struct{
    AdjList vertices;//邻接表
    int vexnum, arcnum;//图的顶点数和弧数
}ALGraph;//ALGraph是以邻接表存储的图类型

【注】若 G 为无向图,则图的邻接表存储方法所需的存储空间为 O(|V|+2|E|) ,若 G 为有向图,则所需的存储空间为 O(|V|+|E|) 。

3. 图的邻接表存储表示法的特点

  • 对于稀疏图(即边数较少的图),采用邻接表表示将极大地节省存储空间。

  • 在邻接表中,给定一个顶点,能很容易地找出它的所有邻边,因为只需要读取它的邻接表。在邻接矩阵中,相同的操作则需要扫描一行,花费的时间为 O(n) 。但是,若要确定给定的两个顶点间是否存在边,则在邻接矩阵中可以立刻查到,而在邻接表中则需要在相应结点对应的边表中查找另一结点,效率较低。

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

  • 图的邻接表表示并不唯一,因为在每个顶点对应的边表中,各边结点的链接次序可以是任意的,它取决于建立邻接表的算法及边的输入次序。

4. 例题

① 以下关于图的存储结构的叙述中,正确的是( B )。
A. 一个图的邻接矩阵表示唯一,邻接表表示唯一
B. 一个图的邻接矩阵表示唯一,邻接表表示不唯一
C. 一个图的邻接矩阵表示不唯一,邻接表表示唯一
D. 一个图的邻接矩阵表示不唯一,邻接表表示不唯一

② 用邻接表法存储图所用的空间大小( A )。
A. 与图的顶点数和边数有关
B. 只与图的边数有关
C. 只与图的顶点数有关
D. 与边数的平方有关

③ 若邻接表中有奇数个边表结点,则( D )。
A. 图中有奇数个结点
B. 图中有偶数个结点
C. 图为无向图
D. 图为有向图

④ 在有向图的邻接表存储结构中,顶点 v 在边表中出现的次数是( C )。
A. 顶点 v 的度
B. 顶点 v 的出度
C. 顶点 v 的入度
D. 依附于顶点 v 的边数

⑤ n 个顶点的无向图的邻接表最多有( B )个边表结点。
A. n2
B. n × (n - 1)
C. n × (n + 1)
D. n × (n - 1) / 2

⑥ 假设有 n 个顶点、e 条边的有向图用邻接表表示,则删除与某个顶点 v 相关的所有边的时间复杂度为( C )。
A. O(n)
B. O(e)
C. O(n + e)
D. O(n × e)

⑦ 对邻接表的叙述中,( D )是正确的。
A. 无向图的邻接表中,第 i 个顶点的度为第 i 个链表中结点数的两倍
B. 邻接表比邻接矩阵的操作更简便
C. 邻接矩阵比邻接表的操作更简便
D. 求有向图结点的度,必须遍历整个邻接表

三、十字链表法

1. 概念

十字链表是有向图的一种链式存储结构。在十字链表中,有向图的每条弧用一个结点(弧结点)来表示,每个顶点也用一个结点(顶点结点)来表示。如下图所示:

弧结点中有 5 个域:

  • tailvex 域指示弧尾顶点的编号;
  • headvex 域指示弧头顶点的编号;
  • 头链域 hlink 指向弧头相同的下一个弧结点;
  • 尾链域 tlink 指向弧尾相同的下一个弧结点;
  • info 域存放该弧的相关信息。

这样,弧头相同的弧在同一个链表上,弧尾相同的弧也在同一个链表上。

顶点结点中有 3 个域:

  • data 域存放该顶点的数据信息,如顶点名称;
  • firstin 域指向以该顶点为弧头的第一个弧结点;
  • firstout 域指向以该顶点为弧尾的第一个弧结点。

2. 有向图的十字链表表示示例

注意:顶点结点之间是顺序存储的,弧结点省略了 info 域。

在十字链表中,既容易找到 vi 为尾的弧,也容易找到 vi 为头的弧,因而容易求得顶点的出度和入度。图的十字链表表示是不唯一的,但一个十字链表表示唯一确定一个图。下面来详细分析一下上面的示例:

十字链表适用于有向图,它实际上是邻接表和逆邻接表的结合。邻接表的单链表指向的是顶点的出边表,与之相反,逆邻接表的单链表指向的是顶点的入边表。

我们先来看顶点的出边表部分:(进行水平方向的连接)

1)v1 指向 v2 、v3

2)v3 指向 v1 、v4
v4 指向 v1 、v2 、v3

再来看顶点的入边表部分:(进行垂直方向的连接)

3)v1 被 v3 、v4 指向:

4)v2 被 v1 、v4 指向
v3 被 v1 、v4 指向
v4 被 v3 指向:

再举一个例子:(自己练习一下吧)

3. 例题

十字链表是( B )的存储结构。
A. 无向图
B. 有向图
C. 无向图和有向图
D. 都不是

四、邻接多重表法

1. 概念

邻接多重表是无向图的一种链式存储结构。在邻接表中,容易求得顶点和边的各种信息,但在邻接表中求两个顶点之间是否存在边而对边执行删除等操作时,需要分别在两个顶点的边表中遍历,效率较低。与十字链表相似,在邻接多重表中,每个顶点、每条边用一个结点表示,其结构如下所示:

边结点中有 5 个域:

  • ivex 域指示该边依附的其中一个顶点的编号;
  • jvex 域指示该边依附的另一个顶点的编号;
  • ilink 域指向下一条依附于顶点 ivex 的边;
  • jlink 域指向下一条依附于顶点 jvex 的边;
  • info 域存放该边的相关信息。

顶点结点中有 2 个域:

  • data 域存放该顶点的相关信息;
  • firstedge 域指向第一条依附于该顶点的边。

在邻接多重表中,所有依附于同一顶点的边串联在同一链表中,因为每条边依附于两个顶点,所以每个边结点同时链接在两个链表中。对无向图而言,其邻接多重表和邻接表的差别仅在于:同一条边在邻接表中用两个结点表示,而在邻接多重表中只有一个结点。

2. 无向图的邻接多重表表示示例

注意:顶点结点之间是顺序存储的,弧结点省略了 info 域,邻接多重表的各种基本操作的实现和邻接表类似。下面来详细分析一下上面的示例:

1)图中顶点 b 和顶点 c 所连的边(3 条)最多,因此先画顶点 b 的边表:
b-a(1-0)、b-c(1-2)、b-e(1-4)

2)除去三条边后,顶点 c 和顶点 d 所连的边(2 条)最多,由于间隔一个顶点画出的图更好看,因此画顶点 d 的边表:
d-a(3-0)、d-c(3-2)

3)最后只剩 e-c(4-2)这条边了,把它加入顶点 e 的边表中:

4)连线,规则是与编号相同的就串联起来,比如顶点 a 的编号为 0 ,那么找到边表中有含 0 的结点,依次串联起来即可:

可以看出邻接多重表的表示方式不唯一。

再举两个例子:(自己练习一下吧)


3. 例题

邻接多重表是( A )的存储结构。
A. 无向图
B. 有向图
C. 无向图和有向图
D. 都不是

图的四种存储方式的总结

邻接矩阵邻接表十字链表邻接多重表
空间复杂度O(V2)无向图:O(V+2E) ,有向图:O(V+E)O(V+E)O(V+E)
找相邻边遍历对应行或列的时间复杂度为O(V)找有向图的入度必须遍历整个邻接表很方便很方便
删除边或顶点删除边很方便,删除顶点需要大量移动数据无向图中删除边或顶点都不方便很方便很方便
适用于稠密图稀疏图和其他只能存有向图只能存无向图
表示方式唯一不唯一不唯一不唯一
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Kusunoki_D

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值