6.2 图的存储

邻接矩阵、邻接表、十字链表与邻接多重表,比较它们的空间开销与适用场景。

考查要求 ★★★ 重点掌握 邻接矩阵邻接表十字链表邻接多重表A^n 的含义

邻接矩阵法

所谓邻接矩阵存储,是指用一个一维数组存储图中顶点的信息,用一个二维数组存储图中边的信息(即各顶点之间的邻接关系),存储顶点之间邻接关系的二维数组称为邻接矩阵。

A[i][j]={1,(vi,vj) 或 ⟨vi,vj⟩ 是 E(G) 中的边0,否则A[i][j] = \begin{cases} 1, & (v_i, v_j) \text{ 或 } \langle v_i, v_j \rangle \text{ 是 } E(G) \text{ 中的边} \\ 0, & \text{否则} \end{cases}

对于带权图(网),若顶点之间有边相连,则邻接矩阵中对应项存放着该边对应的权值;若顶点之间不相连,则用 ∞ 来代表这两个顶点之间不存在边。

#define MaxVertexNum 100
typedef char VertexType;
typedef int EdgeType;
typedef struct {
    VertexType Vex[MaxVertexNum];               // 顶点表
    EdgeType Edge[MaxVertexNum][MaxVertexNum];  // 邻接矩阵,边表
    int vexnum, arcnum;                         // 图的当前顶点数和边数
} MGraph;
交互演示邻接矩阵与邻接表——点击顶点
ABCDE邻接矩阵ABCDEABCDE0101010111010111110001100邻接表A13∧B0243∧C134∧D021∧E12∧顶点编号:A=0 B=1 C=2 D=3 E=4(邻接表结点中存放的是邻接点的编号)
顶点 B 的度 = 第 1 行(或第 1 列)非零元素个数 = 邻接表中链表长度 = 4。无向图的邻接矩阵是对称矩阵,每条边在邻接表中出现两次

邻接矩阵的特点:

  1. 无向图的邻接矩阵一定是一个对称矩阵(并且唯一),因此在实际存储时只需存储上(或下)三角矩阵的元素;
  2. 对于无向图,第 ii 行(或第 ii 列)非零元素的个数正好是顶点 ii 的度;
  3. 对于有向图,第 ii 行非零元素的个数正好是顶点 ii 的出度,第 ii 列非零元素的个数正好是顶点 ii 的入度;
  4. 用邻接矩阵存储图,很容易确定图中任意两个顶点之间是否有边相连;但要确定图中有多少条边,则必须按行、按列对每个元素进行检测,所花费的时间代价很大;
  5. 稠密图适合使用邻接矩阵存储,空间复杂度为 O(n2)O(n^2),与边数无关。

邻接表法

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

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

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;

邻接表的特点:

  1. 若 GG 为无向图,则所需的存储空间为 O(∣V∣+2∣E∣)O(|V| + 2|E|);若 GG 为有向图,则所需的存储空间为 O(∣V∣+∣E∣)O(|V| + |E|)。前者的倍数 2 是因为在无向图中,每条边在邻接表中出现了两次;
  2. 对于稀疏图,采用邻接表表示将极大地节省存储空间;
  3. 在邻接表中,给定一个顶点,能很容易地找出它的所有邻边;但要确定给定的两个顶点间是否存在边,则需要在相应结点对应的边表中查找另一结点,效率较低;
  4. 在有向图的邻接表表示中,求一个给定顶点的出度只需计算其邻接表中的结点个数;但求其顶点的入度则需要遍历全部的邻接表。也可以采用逆邻接表的存储方式来加速求解给定顶点的入度;
  5. 图的邻接表表示并不唯一,因为各边表结点的链接次序可以是任意的。

十字链表

十字链表是有向图的一种链式存储结构。在十字链表中,对应于有向图中的每条弧有一个结点,对应于每个顶点也有一个结点。

弧结点tailvexheadvexhlinktlink(info)tailvex / headvex:弧尾 / 弧头编号hlink:弧头相同的下一条弧tlink:弧尾相同的下一条弧顶点结点datafirstinfirstoutfirstin:以该顶点为弧头的第一条弧firstout:以该顶点为弧尾的第一条弧

在十字链表中,既容易找到 viv_i 为尾的弧,也容易找到 viv_i 为头的弧,因而容易求得顶点的出度和入度。图的十字链表表示是不唯一的,但一个十字链表表示确定一个图。

邻接多重表

邻接多重表是无向图的另一种链式存储结构。在邻接表中,容易求得顶点和边的各种信息,但求两个顶点之间是否存在边而对边执行删除等操作时,需要分别在两个顶点的边表中遍历,效率较低。

与十字链表类似,在邻接多重表中,每条边用一个结点表示,其结构为 ivex | ilink | jvex | jlink | (info),其中 ilink 指向下一条依附于顶点 ivex 的边,jlink 指向下一条依附于顶点 jvex 的边。在邻接多重表中,所有依附于同一顶点的边串联在同一链表中,由于每条边依附于两个顶点,因此每个边结点同时链接在两个链表中。

四种存储方式的比较

邻接矩阵邻接表十字链表邻接多重表
空间复杂度O(∣V∣2)O(\lvert V\rvert ^2)无向 O(∣V∣+2∣E∣)O(\lvert V\rvert + 2\lvert E\rvert );有向 O(∣V∣+∣E∣)O(\lvert V\rvert + \lvert E\rvert )O(∣V∣+∣E∣)O(\lvert V\rvert + \lvert E\rvert )O(∣V∣+∣E∣)O(\lvert V\rvert + \lvert E\rvert )
适用于稠密图稀疏图只能存有向图只能存无向图
表示方式唯一不唯一不唯一不唯一
计算度/出度/入度必须遍历对应行或列计算有向图的度或入度不方便,其余很方便很方便很方便
找相邻的边必须遍历对应行或列,O(∣V∣)O(\lvert V\rvert )找有向图的入边必须遍历整个邻接表很方便很方便
删除边或顶点删除边很方便,删除顶点需要大量移动数据无向图中删除边或顶点都不方便很方便很方便

即时练习

即时练习5 题
点击选项即可作答
  1. 1
    若图 G 有 n 个顶点、e 条边,采用邻接表存储,则求某顶点的入度的时间复杂度为( )(G 为有向图)。
  2. 2
    带权有向图 G 用邻接矩阵存储,则 vᵢ 的入度等于邻接矩阵中( )。
  3. 3
    一个有向图 G 的邻接表存储如下:顶点 0→1→3;顶点 1→2→3;顶点 2→(空);顶点 3→2。现按深度优先遍历,从顶点 0 出发,所得到的顶点序列是( )。
  4. 4
    以下关于图的存储结构的叙述中,正确的是( )。
  5. 5
    用邻接矩阵 A 存储无权有向图,A² 中元素 A²[i][j] 表示( )。
学完了吗?

标记完成后,侧栏会显示进度。进度仅保存在本浏览器中。