第六章 图
第六章 图
6.1 图的定义和基本数据
6.1.1 图的定义
(1)图的核心定义
图是由顶点集V和顶点间的关系集合E(边的集合)组成的一种数据结构,可以用二元组定义为:G=(V,E)。
(2)无向图与有向图的定义及二元组表示
| 类型 | 核心特征 | 二元组表示示例 | 符号区别 |
|---|---|---|---|
| 无向图 | 边无方向性,无箭头标识 | 无向图G1=(V1,E1),其中V1={a,b,c,d},E1={(a,b),(a,c),(a,d),(b,d),(c,d)} | 边集元素用小括号 ()表示,代表两点间无向边 |
| 有向图 | 边有方向性,用箭头标识(边称为 “弧”) | 有向图G2=(V2,E2),其中V2={1,2,3},E2= | 弧集元素用 ** 尖括号 <>** 表示,尖括号内前者为弧尾(箭头起点)、后者为弧头(箭头终点) |
注:顶点集均用 花括号 {} 表示,仅边 / 弧集的符号随图的方向属性变化。
例如,对于图6-1所示的无向图G1和有向图G2,它们的数据结构可以描述为:G1=(V1,E1), 其中 V1={a,b,c,d},E1={(a,b),(a,c),(a,d),(b,d),(c,d)},而G2=(V2,E2),其中V2={1,2,3}, E2={<1,2>,<1,3>,<2,3>,❤️,1>}。

6.1.2 图的基本术语
(1)有向图与无向图(基础分类)
- 有向图:边有方向性,边称为弧,有明确的弧尾(起点)和弧头(终点),用箭头标识。
- 无向图:边无方向性,仅表示两点间存在连接,无箭头标识。
(2)完全图、稠密图、稀疏图

①完全图
定义:任意两个顶点之间都存在边(无向图)或弧(有向图)的图,分为无向完全图和有向完全图。
- 无向完全图:n个顶点的无向完全图,边数为组合数Cn2=n(n−1)/2(推导:任意两点仅需 1 条无向边连接,共需从n个顶点选 2 个的组合数)。例如 4 个顶点的无向完全图,边数为4×3/2=6。
- 有向完全图:n个顶点的有向完全图,弧数为排列数An2=n(n−1)(推导:两点间需 2 条方向相反的弧,弧数为无向完全图边数的 2 倍)。例如 4 个顶点的有向完全图,弧数为4×3=12。
②一般图的边 / 弧数范围
- 无向图:顶点数为n,边数e满足0≤e≤n(n−1)/2。
- 有向图:顶点数为n,弧数e满足0≤e≤n(n−1)。
③稠密图与稀疏图
- 稠密图:边 / 弧数接近对应完全图边 / 弧数的图。
- 稀疏图:边 / 弧数远少于对应完全图边 / 弧数的图(讲课表述中 “析出符” 为口误,实际为稀疏图)。
(3)度、入度、出度
①度的通用定义
图中一个顶点依附的边或弧的数目,称为该顶点的度。
②有向图的入度与出度
有向图中顶点的度为入度与出度之和,具体定义如下:
- 入度:顶点依附的弧头(箭头指向该顶点)的数目,代表进入该顶点的弧的数量。
- 出度:顶点依附的弧尾(箭头从该顶点出发)的数目,代表从该顶点发出的弧的数量。
③无向图的度
无向图中无入度、出度之分,顶点的度即为其相连的边的数目。
典型问题:特殊有向图的形状
若有向图中仅 1 个顶点入度为 0,其余顶点入度均为 1,则该图为有向树(入度为 0 的顶点为树根,其余顶点对应树的子节点,有唯一的双亲节点)。

(4)连通图与强连通图

| 类型 | 适用图 | 核心定义 |
|---|---|---|
| 连通图 | 无向图 | 图中 任意两个顶点v和u 都存在从v到u的路径 |
| 强连通图 | 有向图 | 图中 任意两个顶点v和u 都存在双向路径(既存在v到u的路径,也存在u到v的路径) |
反例:若无向图中存在两个顶点组(如V0−V1−V2−V3和V4−V5),两组内顶点互通但组间无路径,则为非连通图;有向图中若存在顶点对无双向路径(如V1无法到V2),则为非强连通图。
(5)网
图的边 / 弧上附带的相关数值称为权(可表示距离、耗费等),带权的图称为网,也叫有权图。
(6)子图
设两个图G=(V,{E})和G1=(V1,{E1}),若满足V1⊆V且E1⊆E,则称G1是G的子图。例如课件中图 (a) 的顶点和边的部分子集构成的图 (b)、图 (c),均为图 (a) 的子图。

6.2 图的存储结构

6.2.1 邻接矩阵(顺序存储,底层为二维数组)
(1) 邻接矩阵的存储原理
在邻接矩阵表示中,除了存放顶点本身信息外,还用一个矩阵表示各个顶点之间的关系。若(i,j)∈E(G)或〈i,j〉∈E(G),则矩阵中第i行 第j列元素值为1,否则为0 。
图的邻接矩阵定义为:
邻接矩阵通过顶点表存储顶点本身信息,通过二维矩阵存储顶点间的邻接关系:
- 若两顶点间存在边 / 弧,矩阵对应位置值为1(普通图)或边 / 弧的权值(网);
- 若不存在边 / 弧,普通图对应位置为0,网对应位置为 ∞;
- 顶点自身到自身(主对角线):考试建议记为0,教材通常记为 ∞,需注意题目约定。
(2)不同类型图的邻接矩阵表示
①无向图的邻接矩阵



-
构建步骤:
① 按顶点数构建
n×n矩阵(5 顶点为5×5),初始化全为 0;② 若顶点
Vi与Vj有边,将矩阵[i][j]和[j][i]置为 1(无向边双向对称)。 -
讲课易错点:无向图 7 条边对应矩阵 14 个 1(每条边被双向记录,边数 = 矩阵中 1 的个数 / 2)。
-
核心特点:
- 矩阵对称;
- 第i行或第i 列1的个数为顶点i 的度;
- 完全无向图的邻接矩阵主对角线为 0,其余位置全为 1;
- 矩阵中1的个数的一半为图中边的数目
- 可通过
[i][j]是否为 1,快速判断Vi与Vj是否有边; - 占用的存储单元数目为n+2e。



②有向图的邻接矩阵
-
构建约定:矩阵记录顶点发出去的弧(出度边),即
[i][j]=1表示存在<Vi,Vj>的弧。 -
构建步骤(以4 顶点有向图为例):
① 构建
4×4矩阵并初始化全为 0;② 若
Vi发出弧至Vj,仅将[i][j]置为 1(入度边不记录在该行)。 -
核心特点:
- 矩阵不一定对称(有向弧单向性);
- 顶点
i的出度= 第i行 1 的个数,入度= 第i列 1 的个数,总度数 = 出度 + 入度; - 矩阵中 1 的个数 = 图中弧的总数(无重复记录);
- 完全有向图的邻接矩阵主对角线为 0,其余位置全为 1;
- 很容易判断顶点i 和顶点j 是否有弧相连。

③网的邻接矩阵(带权图)
-
课件定义公式:
\[A[i][j] = \begin{cases} w_{ij} & \text{若}(i,j) \in E(G) \text{或} <i,j> \in E(G) \\ 0 & \text{若} i = j \\ \infty & \text{其它情形} \end{cases} \] -
构建注意事项(讲课重点强调):
① 权值wij替换普通图的 “1”,表示两顶点间边的权重;
② 主对角线的 0/∞需按题目要求选择(考试优先用 0);
③ 无向网矩阵对称,有向网矩阵不对称。




(3)邻接矩阵的优缺点(扩展补充)
- 优点:判断两顶点是否邻接效率高(O (1));计算顶点度(无向图)、出 / 入度(有向图)方便;
- 缺点:空间复杂度高(O (n²)),适合存储稠密图;稀疏图会造成大量空间浪费。
6.2.2 邻接表(链式存储,头节点 + 表节点)
(1)邻接表的存储结构(课件定义)

把同一个顶点发出的边链接在同一个边链表中,链表的每一个结点代表一条边,叫做表结点(边结点),邻接点域adjvex保存与该边相关联的另一顶点的顶点下标 , 链域next存放指向同一链表中下一个表结点的指针 ,数据域weight存放边的权。边链表的表头指针存放在头结点中。头结点以顺序结构存储,其数据域info存放顶点信息,链域first指向链表中第一个顶点
邻接表由头节点数组和表节点链表组成,网的表节点需额外增加权值域:
| 节点类型 | 结构组成 | 说明 |
|---|---|---|
| 头节点 | data(顶点信息)+firstarc(指向首个表节点的指针) |
按顶点顺序存储,形成头节点数组 |
| 普通图表节点 | adjvex(邻接点的顶点下标)+nextarc(指向下一表节点的指针) |
存储顶点的出度边 / 邻接边 |
| 网的表节点 | adjvex+weight(边的权值)+nextarc |
比普通图表节点多权值域,记录边的权重 |
(2)不同类型图的邻接表表示
①无向图的邻接表

- 构建逻辑:每个顶点的头节点链表,存储所有与该顶点邻接的顶点下标(无向边双向存储,如Vi邻接Vj,则Vi链表有
j,Vj链表有i)。 - 讲课示例:5 顶点无向图中,V1的表节点为 V2(下标 3)、V4(下标 1),顺序可任意。
- 核心特点:
- 邻接表表示不唯一(表节点链入顺序可自由调整);
- 第
i个头节点的链表节点数 = 顶点i的度; - 所有表节点总数 = 2× 边数(无向边双向记录);
- 空间复杂度为O(n+2e)(n 为顶点数,e 为边数)。
②有向图的邻接表与逆邻接表

-
邻接表(出边表):
① 存储逻辑:仅记录顶点的出度边,即表节点为顶点发出弧的终点下标;
② 特点:第
i个头节点的链表节点数 = 顶点i的出度;空间复杂度为O(n+e)(每条弧仅记录一次);无法直接求入度。 -
逆邻接表(入边表):
① 存储逻辑:记录顶点的入度边,即表节点为指向该顶点的弧的起点下标;
② 作用:解决有向图邻接表无法直接求入度的问题,第
i个头节点的链表节点数 = 顶点i的入度。 -
讲课易错点:无特殊说明时,有向图邻接表默认指出边表。
③网的邻接表
-
构建逻辑:表节点增加
weight域存储边的权值,其余结构与普通有向 / 无向图邻接表一致; -
讲课典型例题:已知网的邻接表重构网
① 核心方法:根据头节点的顶点信息,遍历每个头节点的表节点,按
adjvex(邻接点下标)和weight(权值)绘制带权边;② 意义:验证了存储结构可反向恢复逻辑结构,体现数据结构 “存储 - 还原” 的核心价值。

(3)邻接表的优缺点(扩展补充)
- 优点:空间利用率高(O (n+e)),适合存储稀疏图;便于遍历顶点的邻接边;
- 缺点:判断两顶点是否邻接效率低(需遍历对应头节点的链表);无向图存在边的重复存储。
6.2.3邻接矩阵与邻接表的核心对比
| 对比维度 | 邻接矩阵 | 邻接表 |
|---|---|---|
| 存储结构 | 二维数组(顺序存储) | 头节点数组 + 链表(链式存储) |
| 空间复杂度 | O (n²)(与边数无关) | 无向图 O (n+2e)、有向图 O (n+e) |
| 邻接判断效率 | O (1)(直接查矩阵下标) | O (d)(d 为对应顶点的度,需遍历链表) |
| 顶点度计算 | 无向图:行 / 列 1 的个数;有向图:行 = 出度、列 = 入度 | 无向图:链表节点数;有向图:邻接表 = 出度、逆邻接表 = 入度 |
| 适用场景 | 稠密图、需快速判断邻接的场景 | 稀疏图、需频繁遍历邻接边的场景 |
6.2.4讲课中的考试与学习注意事项
- 邻接矩阵主对角线约定:考试建议主对角线记为 0,教材常用∞,需严格遵循题目要求;
- 有向图邻接矩阵的边数:矩阵中 1 的个数 = 弧的总数(无向图为 1 的个数 / 2);
- 邻接表的唯一性:表节点顺序不影响存储有效性,因此同一图的邻接表表示不唯一;
- 知识衔接:掌握图的存储结构后,后续将学习图的核心操作 ——图的遍历(深度优先 / 广度优先),为后续最小生成树、最短路径等应用打基础。
6.3 图的遍历
6.3.1图的遍历概述
(1)定义
从图中某一顶点出发,访遍图中其余所有顶点且每个顶点仅被访问一次的操作,称为图的遍历。
(2)核心意义
图的遍历算法是求解图的连通性问题、拓扑排序、关键路径等后续图应用算法的基础,是图论算法体系的核心前置知识点。
(3)两种经典遍历方法
深度优先搜索(DFS)遍历、广度优先搜索(BFS)遍历,两种方法均可实现对图中所有顶点的不重复访问,且遍历序列不唯一。
6.3.2 深度优先搜索(DFS)遍历
(1)算法核心思想
类似于树的先序遍历,遵循 “先深后广、回溯探索” 的原则,优先沿着一条路径访问到尽头,再回溯到上一顶点探索其他分支。
(2)算法执行步骤(课件标准流程)
- 首先访问顶点i,并将其访问标记置为已访问(即
visited[i]=1,初始化时所有顶点visited值为 0); - 搜索与顶点i有边相连的下一个顶点j,若j未被访问,则访问j并标记
visited[j]=1,再从j开始重复此探索过程;若j已访问,则继续查找与i相连的其他顶点; - 若与i相连的所有顶点均已访问,回溯到上一个访问的顶点,重复上述探索过程,直至图中所有顶点都被访问完毕。
(3)辅助存储结构
遍历过程需借助栈实现回溯,栈遵循 “后进先出” 原则,用于记录访问路径,当当前顶点分支探索完毕时,从栈中弹出顶点实现回溯。
(4)典型示例(无向图 G7,从顶点 0 出发)
例如,对所示无向图G7,从各个顶点出发的深度优先搜索遍历序列可有多种。
在无向图G7中,从顶点0出发的深度优先搜索遍历序列举如下:

- 初始访问顶点 0,标记
visited[0]=1,选择其邻接顶点 1 进行访问,标记visited[1]=1; - 从顶点 1 出发,选择未访问的邻接顶点 4,标记
visited[4]=1,再从 4 出发访问邻接顶点 5,标记visited[5]=1; - 顶点 5 的邻接顶点(4、1)均已访问,回溯至顶点 4,其邻接顶点(1、5)也已访问,继续回溯至顶点 1;
- 顶点 1 剩余未访问邻接顶点 6,访问 6 并标记
visited[6]=1,再从 6 出发访问未访问的邻接顶点 2,标记visited[2]=1; - 顶点 2 的邻接顶点(0、6)均已访问,依次回溯至 6、1,再回溯至顶点 0;
- 顶点 0 剩余未访问邻接顶点 3,访问 3 并标记
visited[3]=1,至此所有顶点访问完毕。- 其中一个合法遍历序列:0→1→4→5→6→2→3(序列不唯一)。
(5)关键结论与考点
- 遍历起点可任意选择,考试中常指定起点;
- 同一顶点的多个未访问邻接顶点可任选访问顺序,因此DFS 遍历序列不唯一;
- 考试常考 “判断给定序列是否为合法 DFS 遍历序列”,需验证序列是否符合 “先深后广、回溯探索” 的逻辑。
6.3.3 广度优先搜索(BFS)遍历
(1)算法核心思想
遵循 “先广后深、分层访问” 的原则,先访问起始顶点的所有邻接顶点(同层顶点),再依次访问各邻接顶点的邻接顶点(下一层顶点)。
(2)算法执行步骤(课件标准流程)
- 初始化队列并置空,访问起始顶点后将其入队;
- 若队列非空,取出队头顶点,依次访问其所有未被访问的邻接顶点,将这些顶点标记为已访问并入队;
- 重复步骤 2,直至队列为空;若此时仍有未访问顶点,需另选起点重复遍历;
- 队列为空时,本次遍历结束。
(3)辅助存储结构
遍历过程需借助队列实现分层访问,队列遵循 “先进先出” 原则,保证同层顶点按入队顺序依次处理。
(4)典型示例(无向图 G7,从顶点 1 出发)
例如,对下图所示无向图G7,从顶点1出发的广度优先搜索遍历序列可有多种,下面仅给出三种,其它可作类似分析。
在无向图G7中,从顶点1出发的广度优先搜索遍历序列举如下:

- 初始访问顶点 1,入队;出队后访问其所有未访问邻接顶点 2、3(可互换顺序),标记后入队;
- 队头顶点 2 出队,访问其未访问邻接顶点 4、5(可互换顺序),标记后入队;
- 队头顶点 3 出队,访问其未访问邻接顶点 6、7(可互换顺序),标记后入队;
- 队头顶点 4 出队,访问其未访问邻接顶点 8,标记后入队;
- 依次处理队中剩余顶点 5、6、7、8,均无未访问邻接顶点,队列最终为空,遍历完成。
- 合法遍历序列示例:1→2→3→4→5→6→7→8、1→3→2→7→6→5→4→8 等(序列不唯一)。
例如,对下图所示无向图G7,从顶点1出发的广度优先搜索遍历序列可有多种,下面仅给出三种,其它可作类似分析。
在无向图G7中,从顶点1出发的广度优先搜索遍历序列举三种为:
1, 2, 3, 4, 5, 6, 7, 8
1, 3, 2, 7, 6, 5, 4, 8
1, 2, 3, 5, 4, 7, 6, 8
(5)快速判断合法 BFS 序列的技巧
可将遍历序列按 “层” 划分:起始顶点为第 0 层,其邻接顶点为第 1 层,第 1 层顶点的邻接顶点为第 2 层,以此类推。同层顶点的顺序可任意互换,只要满足 “先访问上层顶点,再访问下层顶点” 即可判定为合法序列。
6.4 图的应用.
6.4.1生成树和最小生成树
(1)核心思想:复杂问题简化(图→树)
计算机解决复杂结构问题的通用逻辑:多对多的图→一对多的树→线性结构,其中树可进一步简化为二叉树(任意树可与二叉树相互转换,森林可拆解为多棵树再转二叉树),实现难→易的问题拆解。
(2)生成树的基础概念
①图与树的本质区别
- 树:一对多的层次结构,有唯一根节点(无入边),无回路;
- 图:多对多的网状结构,无固定根节点,可存在回路。
②生成树的定义
在图论中,常常将树定义为一个无回路连通图。例如,图6-18中的两个图就是无回路的连通图。乍一看它似乎不是不是树,但只要选定某个顶点做根并以树根为起点对每条边定向,就可以将它们变为通常的树。

对连通图而言,若子图满足两个条件:① 包含原图所有n个顶点;② 仅用n-1条边连接所有顶点且无回路,则该子图为原图的生成树。
③图转生成树的两种方法
基于图的两种遍历方式,可生成对应类型的生成树,考研填空题常考:
-
深度优先搜索生成树:依赖栈结构实现遍历,遍历路径构成的树;
-
广度优先搜索生成树:依赖队列结构实现遍历,遍历路径构成的树。

④生成树的判定依据(考研简答题要点)
需同时满足:① 顶点数n与边数m满足m = n-1;② 无回路;③ 保持原图的连通性(所有顶点均被连接)。
(3)最小生成树的概念与意义
①前置概念:网
带权值的图称为网(区别于无权图),权值可表示路径长度、运输成本等实际意义。
②最小生成树的定义
在一般情况下,图中的每条边若给定了权,这时,我们所关心的不是生成树,而是生成树中边上权值之和。若生成树中每条边上权值之和达到最小,称为最小生成树。
③实际应用场景
如全国城市间修路 / 架线时,实现所有城市连通且总成本 / 总路程最低,核心是求解最小生成树。
④最小生成树的两种经典算法
考研核心考察,需区分两种算法的流程与适用场景:
- 普里姆(Prim)算法:本节重点;
- 克鲁斯卡尔(Kruskal)算法:6.4.3 节内容(后续学习)。
6.4.2 普里姆(Prim)算法
(1)算法核心思想
在图中任取一个顶点K作为开始点,令U={k},W=V-U,其中V为图中所有顶点集,然后找一个顶点在U中,另一个顶点在W中的边中最短的一条,找到后,将该边作为最小生成树的树边保存起来,并将该边顶点全部加入U集合中,并从W中删去这些顶点,然后重新调整U中顶点到W中顶点的距离, 使之保持最小,再重复此过程,直到W为空集止。
(2)三个核心集合的定义(必须熟记)
- 顶点全集 V:图中所有顶点的集合,全程不变化;
- 已选顶点集 U:初始时仅包含任选的一个起点顶点,后续逐步加入新顶点;
- 未选顶点集 W:W = V - U,随 U 的扩充逐步缩小,最终为空集时算法终止。
(3)算法执行流程(考研核心,需掌握步骤逻辑)
1.初始化阶段
- 从 V 中任选一个起点顶点(如顶点 1)加入 U,此时U={1},W={剩余所有顶点};
- 用虚线连接 U 中顶点与 W 中所有顶点,标注对应权值(无直接边的顶点间权值记为无穷大)。
2.迭代选边与扩充集合阶段
- 第一步:寻找 U 与 W 之间权值最小的边,将其由虚线改为实线(作为最小生成树的边保留);
- 第二步:将该最小边的 W 侧顶点并入 U,同时 W 中移除该顶点(U 扩充,W 缩小);
- 第三步:修正权值(关键步骤):因 U 中新增顶点,需重新计算 W 中各顶点到 U 的权值(取与 U 中所有顶点边权的最小值,保留最小权值的边,删除原较大权值的边)。
3.终止条件
当U = V(即 W 为空集)时,算法终止,此时所有实线边构成最小生成树。
(4)算法实操案例(以 6 个顶点的无向连通网为例,起点为顶点 1)
-
初始化:U={1},W={2,3,4,5,6},1 与 2/3/4 的权值为 6/1/5,与 5/6 的权值为无穷大,均为虚线连接;

-
第一次迭代
- 最小权边为 1-3(权值 1),实线保留,U={1,3},W={2,4,5,6};
- 修正权值:3 与 2/5/6 的权值为 5/5/4,对比原 1-2(6)、1-5(无穷)、1-6(无穷),更新 2 的权为 5、5 的权为 5、6 的权为 4,1-4 的权 5(3-4 权 7,保留原权);

-
第二次迭代
- 最小权边为 3-6(权值 4),实线保留,U={1,3,6},W={2,4,5};
- 修正权值:6 与 4 的权为 2(小于原 1-4 的 5),更新 4 的权为 2;6 与 2/5 的权为无穷 / 6,保留原权;

-
第三次迭代
-
最小权边为 6-4(权值 2),实线保留,U={1,3,6,4},W={2,5};
-
修正权值:4 与 2/5 无直接边(权为无穷),保留原权(2 的权 5、5 的权 5);

-
-
第四次迭代
- 最小权边为 3-2(权值 5),实线保留,U={1,3,6,4,2},W={5};
- 修正权值:2 与 5 的权为 3(小于原 3-5 的 5),更新 5 的权为 3;

-
第五次迭代
- 最小权边为 2-5(权值 3),实线保留,U={1,3,6,4,2,5},W=∅,算法终止。

(5)算法规律与考研注意要点
①步骤数量规律
若图有n个顶点,除原图外需绘制n个阶段图(含 1 次初始化 +n-1次迭代),最终生成n-1条实线边(符合生成树边数要求)。
②考研答题技巧
若要求书写算法,可采用伪代码形式(无需严格 C/Java 语法),核心是体现 “集合划分 - 选最小边 - 扩充集合 - 修正权值” 的逻辑,即可获得高分。
③算法适用场景(扩展,衔接后续克鲁斯卡尔算法)
普里姆算法按顶点为单位处理,时间复杂度主要与顶点数相关,适合稠密图(顶点少、边多的图);后续克鲁斯卡尔算法按边为单位处理,适合稀疏图(顶点多、边少的图)。
6.4.3 克鲁斯卡尔(kruskal)算法
(1)算法基本思想
-
克鲁斯卡尔算法的基本思想是:将图中所有边按权值递增顺序排列,依次选定取权值较小的边,但要求后面选取的边不能与前面选取的边构成回路,若构成回路,则放弃该条边,再去选后面权值较大的边,n个顶点的图中,选够n-1条边即可。
-
讲课通俗解释:“先把所有边按权值从小到大排好队,每次挑最小的边,只要这条边不跟之前选的边连成圈(回路)就留下,等凑够
n-1条边就停 —— 因为每次都选最小的边,最后所有边的权值加起来肯定是最小的”。
例如,对图 中无向网,用克鲁斯卡尔算法求最小生成树的过程见图。

(2)核心步骤

“6 个顶点(1~6)无向网” 为例,边权关系(按讲课描述推导):1-3(权最小为1)、4-6(次小为2)、2-5(权较小为3)、3-6(权为4)、1-4(权为5)、2-3(权为5)、3-5(权较小为5)、1-2(权为6)、5-6(权为6)、3-7(权为7),步骤如下:
| 步骤 | 操作内容 | 已选边数 | 是否构环 | 结果(保留 / 丢弃) |
|---|---|---|---|---|
| 1 | 对所有边按权值递增排序:1-3 < 4-6 < 2-5 < 3-6 < ... | - | - | 排序完成 |
| 2 | 选权最小的边 1-3 | 1 | 无环 | 保留 |
| 3 | 选次小边 4-6 | 2 | 无环 | 保留 |
| 4 | 选边 2-5 | 3 | 无环 | 保留 |
| 5 | 尝试选边 3-6 | 4 | 无环 | 丢弃 |
| 6 | 选边 1-4:与已选边 1-3、3-6 、4-1构成回路(1-3-6-4) | - | 有环 | 保留 |
| 7 | 选边 2-3(或3-5,满足n-1=5条) |
5 | 无环 | 保留 |
| 8 | 终止:已选 5 条边(6-1=5),构成最小生成树 | 5 | - | 算法结束 |
(3)关键注意点(课件结论 + 讲课强调)
-
适用场景:仅针对无向网(最小生成树的本质是 “连接所有顶点且无回路”,有向图不存在生成树概念),与 6.4.2 普里姆算法一致。
-
与普里姆算法的核心区别:
对比维度 克鲁斯卡尔算法 普里姆算法 出发角度 从 “边” 出发(按权选边) 从 “顶点” 出发(扩充顶点集) 适用图类型 稀疏图更优(边少,排序效率高) 稠密图更优(顶点少,更新快) 核心逻辑 避环(选边不构环) 扩点(逐步加顶点,更短距离) -
回路判断方法(课件未详述,补充扩展):
实际实现中需用并查集(DSU,Disjoint Set Union) :
- 初始化每个顶点为独立集合(根节点为自身);
- 选边
(u,v)时,查找u和v的根节点:- 若根节点相同:
u和v已在同一集合,选边必构环,丢弃; - 若根节点不同:合并两个集合,保留边
(u,v)。
- 若根节点相同:
(4)实例结果(讲课案例总结)
- 顶点数
n=6,最终选取 5 条边:1-3、4-6、2-5、3-6、2-3或者3-5; - 所有保留边的权值之和最小,且无回路,构成 “克鲁斯卡尔最小生成树”。
6.4.4.单源点最短路径
(1)最短路径
交通网络中常常提出这样的问题:从甲地到乙地之间是否有公路连通?在有多条通路的情况下,哪一条路最短? 交通网络可用带权图来表示。顶点表示城市名称,边表示两个城市有路连通,边上权值可表示两城市之间的距离、交通费或途中所花费的时间等。求两个顶点之间的最短路径,不是指路径上边数之和最少,而是指路径上各边的权值之和最小。
另外,若两个顶点之间没有边,则认为两个顶点无通路,但有可能有间接通路(从其它顶点达到)。路径上的开始顶点(出发点)称为源点,路径上的最后一个顶点称为终点,并假定讨论的权值不能为负数。
(2)基础概念与算法思想
①单源点最短路径定义:
给定一个出发点(单源点)和一个有向网G=(V,E),求出源点到其它各顶点之间的最短路径。
通俗比喻:“把源点当成北京,其他顶点当成各个城市,求从北京到每个城市的‘总花费最少’的路线(花费对应权值,可能是距离、时间、钱),哪怕中转几次,只要总花费比直达少就选中转路线”。
那么怎样求出单源点的最短路径呢?我们可以将源点到终点的所有路径都列出来,然后在里面选最短的一条即可。但是这样做,用手工方式可以,当路径特别多时,显得特别麻烦,并且没有什么规律,不能用计算机算法实现。
迪杰斯特拉(Dijkstra)在做了大量观察后,首先提出了按路长度递增序产生各顶点的最短路径算法,我们称之为迪杰斯特拉算法。
②算法思想:
算法的基本思想是:设置并逐步扩充一个集合S,存放已求出其最短路径的顶点,则尚未确定最短路径的顶点集合是V-S,其中V为网中所有顶点集合。按最短路径长度递增的顺序逐个以V-S中的顶点加到S中,直到S中包含全部顶点,而V-S为空。
分集合:S(已确定最短路径的顶点)、W=V-S(未确定的顶点);
初始态:S仅含源点,W含其他所有顶点,记录源点到各顶点的初始距离(直达边为权值,无直达边为∞(无穷大));
迭代过程:每次从W中选 “源点到该顶点距离最小” 的顶点,加入S;然后通过该新顶点 “中转”,修正W中其他顶点的距离(若中转距离比原距离短,则更新);
终止:S包含所有顶点(W为空),此时记录的距离即为源点到各顶点的最短路径长度。
具体做法是:设源点为Vl,则S中只包含顶点Vl,令W=V-S,则W中包含除Vl外图中所有顶点,Vl对应的距离值为0,W中顶点对应的距离值是这样规定的:若图中有弧<Vl,Vj>则Vj顶点的距离为此弧权值,否则为∞(一个很大的数),然后每次从W中的顶点中选一个其距离值为最小的顶点Vm加入到S中,每往S中加入一个顶点Vm,就要对W中的各个顶点的距离值进行一次修改。若加进Vm做中间顶点,使<Vl,Vm>+<Vm,Vj>的值小于<Vl,Vj>值,则用<Vl,Vm>+<Vm,Vj>代替原来Vj的距离,修改后再在W中选距离值最小的顶点加入到S中,如此进行下去,直到S中包含了图中所有顶点为止


(2)核心步骤(课件流程 + 讲课详细实例)
讲课实例:有向网含 5 个顶点(1~5),源点为 1,边权关系(按讲课描述整理):
- 直达边:1→2(权 3)、1→5(权 30);1→3(无,
∞)、1→4(无,∞); - 中转边:2→3(权 25)、2→4(权 8)、4→3(权 4)、4→5(权 12)、3→5(权 10)。
步骤 1:初始化
-
集合:
S={1}(源点),W={2,3,4,5}; -
距离数组(
dist[顶点],表示源点 1 到该顶点的距离):dist[1]=0(源点到自身),dist[2]=3,dist[3]=∞,dist[4]=∞,dist[5]=30。
步骤 2:第一次选顶点 + 修正距离
- 选
W中dist最小的顶点:2(dist=3),加入S→S={1,2}; - 修正
W(3,4,5)的距离(通过顶点 2 中转):dist[3]:原∞→ 1→2→3(3+25=28),更新为 28;dist[4]:原∞→ 1→2→4(3+8=11),更新为 11;dist[5]:1→2→5(无此边,∞),比原 30 大,不更新;
- 此时
dist:[0,3,28,11,30](索引 1~5)。
步骤 3:第二次选顶点 + 修正距离
- 选
W中dist最小的顶点:4(dist=11),加入S→S={1,2,4}; - 修正
W(3,5)的距离(通过顶点 4 中转):dist[3]:原 28 → 1→2→4→3(3+8+4=15),更新为 15;dist[5]:原 30 → 1→2→4→5(3+8+12=23),更新为 23;
- 此时
dist:[0,3,15,11,23]。
步骤 4:第三次选顶点 + 修正距离
- 选
W中dist最小的顶点:3(dist=15),加入S→S={1,2,4,3}; - 修正
W(5)的距离(通过顶点 3 中转):dist[5]:原 23 → 1→2→4→3→5(3+8+4+10=25),比 23 大,不更新;
- 此时
dist:[0,3,15,11,23]。
步骤 5:第四次选顶点 + 终止
-
选
W中仅剩的顶点:5(dist=23),加入S→S={1,2,4,3,5}(包含所有顶点); -
终止,最终最短距离:
1→2(3)、1→4(11)、1→3(15)、1→5(23)。
(3)关键注意点(课件结论 + 讲课强调)
-
适用场景:仅针对有向网,且边权必须为非负数(若含负权边,会导致已加入
S的顶点距离被后续负权路径更新,破坏算法逻辑,后续需学贝尔曼 - 福特算法解决)。 -
与最小生成树的核心区别(讲课重点):
对比维度 单源点最短路径(迪杰斯特拉) 最小生成树(克鲁斯卡尔 / 普里姆) 图类型 有向网 无向网 核心目标 源点到每个顶点的 “路径权和最小” 连接所有顶点的 “边权总和最小” 路径特性 有方向(如 1→2 存在,2→1 可能不存在) 无方向(边是双向的) 结果形式 距离数组(源点到各顶点的最短距离) n-1条边(构成树) -
终止条件:
S包含所有顶点(W为空),此时dist数组的每个值就是源点到对应顶点的最短路径长度。
(4)扩展说明(课件未详述,补充实用内容)
-
松弛操作(Relaxation):算法中 “修正距离” 的本质的是松弛操作 —— 对边
(u,v),若dist[v] > dist[u] + weight(u,v),则更新dist[v] = dist[u] + weight(u,v),即 “通过u中转比直达v更近,就更新v的距离”。 -
算法优化(实际实现):
手动计算时可直接选
W中最小dist顶点,但代码实现中用优先队列(小根堆)优化该步骤,时间复杂度从
O(n²)(稠密图)降至O(m log n)(m为边数,稀疏图更优)。 -
无法处理的场景:
若有向网含负权边或负权回路(如边权为 - 2),迪杰斯特拉算法失效,需改用贝尔曼 - 福特算法(检测负权回路)或弗洛伊德算法(求所有顶点对的最短路径,可处理负权边但不能处理负权回路)。
(5)所有顶点对之间的最短路径
顶点对之间的最短路径概念
所有顶点对之间的最短路径是指:对于给定的有向网G=(V,E),要对G中任意一对顶点有序对V、W(V≠W),找出V到W的最短距离和W到V的最短距离。
解决此问题的一个有效方法是:轮流以每一个顶点为源点,重复执行迪杰斯特拉算法n次,即可求得每一对顶点之间的最短路径,总的时间复杂度为O(n3)。
6.4.5拓扑排序(Topological Sort)
(1)拓扑排序的核心概念
-
定义:对 “顶点表示活动、边表示活动优先顺序” 的有向无环图(DAG),按 “先完成前驱活动,再开始后继活动” 的规则,输出所有顶点的有序序列,称为拓扑排序。
-
生活 / 工程关联:
-
课程安排:先修课(前驱活动)完成后,才能学后续课(后继活动),如 “数据结构” 需先学 “程序设计基础(C2)” 和 “离散数学(C3)”。
-
工程调度:大工程拆分为子活动(如 “采购材料”“搭建框架”),需按先后顺序执行,才能省时高效。
-
拓扑排序正是解决 “有优先关系的活动排序” 的核心算法。
(2)核心基础:AOV 网(Activity On Vertex Network)
- AOV 网定义:Activity On Vertex Network(顶点表示活动的网),是拓扑排序的核心数据结构,满足两个特性:
- 边的含义:若存在有向边 ` 活动 I 是活动 J 的直接前驱,J 是 I 的直接后继(I 必须先完成,J 才能开始)。
- 无回路 + 反自反性:不存在 “活动 I 是自身前驱 / 后继” 的情况,也无有向环(否则会出现 “先做 A 才能做 B,先做 B 才能做 A” 的矛盾,无法排序)。
(3)拓扑排序的定义与步骤
-
定义:对 AOV 网的顶点进行线性排序,使得对任意有向边 <i,j>,活动 i 在排序序列中均位于活动 j 之前(满足优先关系)。
-
核心步骤(课件三步法,结合 “课程安排” 实例推演):
| 步骤 | 具体操作 | 关键说明 |
|---|---|---|
| 1 | 在 AOV 网中找到入度为 0的顶点(无前驱活动,可直接开始),将其输出 | 入度:指向该顶点的边的数量(前驱活动数量),入度为 0 表示无前置约束 |
| 2 | 从 AOV 网中删除该顶点,以及所有从该顶点出发的有向边(解除对后继活动的约束) | 删除边后,需更新后继顶点的入度(如删除1,C8>,C8 的入度减 1) |
| 3 | 重复步骤 1-2,直到两种情况之一: 所有顶点均输出(拓扑排序成功,AOV 网无环);② 无入度为 0 的顶点但仍有未输出顶点(排序失败,AOV 网有环) | 核心功能:既输出活动顺序,又能判断 AOV 网是否有环(工程 / 课程安排中需避免环) |
(4)实例解析(计算机专业课程安排)
- 已知条件:部分课程及先修关系(如 C1 = 高等数学,无先修课;C4 = 数据结构,先修 C2、C3;C6 = 编译原理,先修 C5、C7),构建 AOV 网后执行拓扑排序:
- 初始入度为 0 的顶点:C1(高数)、C2(程序设计基础)→ 输出 C1;
- 删除 C1 及边 ``→ C3 入度 = 1(原 2),C8 入度 = 0 → 此时入度为 0 的顶点:C2、C8 → 输出 C2、C8;
- 删除 C2(边2,C3>)、C8(无出边)→ C3 入度 = 0,C9 入度 = 0 → 输出 C3、C9;
- 删除 C3(边3,C4>)、C9(无出边)→ C4 入度 = 0,C5 入度 = 0 → 输出 C4、C5;
- 删除 C4(边4,C7>)、C5(边5,C7>)→ C7 入度 = 0 → 输出 C7;
- 删除 C7(边7,C6>)→ C6 入度 = 0 → 输出 C6;
- 结果:所有顶点输出(C1→C2→C8→C3→C9→C4→C5→C7→C6),排序成功,无环。
(5)扩展应用与注意事项
- 应用场景:课程表生成、项目进度规划(如建筑工程 “打地基→砌墙→封顶”)、依赖包安装(如软件安装时先装依赖组件)。
- 注意事项:拓扑排序结果不唯一(如步骤 2 中可先输出 C8 再输出 C2),只要满足前驱 - 后继关系即可;若 AOV 网有环,需先破除环(如修改课程先修关系)。
6.4.6 关键路径
(1)关键路径的核心问题
- 解决两个核心需求(基于带权有向图):
- 工程最短完成时间:从工程开始到结束,最少需要多少时间(如建筑工程最少 60 天);
- 确定关键活动:若某活动延误,会导致整个工程延误,该活动称为 “关键活动”;关键活动连成的路径,称为 “关键路径”(需重点监控)。
(2)关键路径的基础 ——AOE 网
- AOE 网定义:Activity On Edge Network(边表示活动、顶点表示事件的网),与 AOV 网的核心区别如下:
| 对比维度 | AOV 网(拓扑排序用) | AOE 网(关键路径用) |
|---|---|---|
| 顶点含义 | 活动(如 “学数据结构”) | 事件(如 “数据结构学完”“工程开工”) |
| 边的含义 | 活动优先顺序(无权重) | 活动 + 活动持续时间(有权重,如 “打地基需 10 天”) |
| 核心特性 | 无环、反自反 | 有一个原点(起始事件,如 V1 = 工程开工)、一个汇点(结束事件,如 V9 = 工程竣工) |
- 事件的意义:顶点(事件)发生,意味着所有指向该顶点的活动(边)均已完成,且从该顶点出发的活动(边)可开始。例如:AOE 网中顶点 V5 发生,说明 “活动 A7(V3→V5)”“活动 A8(V4→V5)” 已完成,“活动 A9(V5→V7)” 可开始。
(3)关键路径的核心变量(5 个)
需先计算 4 个基础变量,再推导 1 个关键变量:
| 变量符号 | 含义 | 计算规则 |
|---|---|---|
| Ve(i) | 事件 i 的最早发生时间 | 从原点到顶点 i 的最长路径长度(因需等所有前驱活动完成,取最长时间) |
| Vl(i) | 事件 i 的最晚发生时间 | 从顶点 i 到汇点的最长路径长度,再用 “汇点的 Ve值 - 该长度”(不延误总工期的前提下,事件 i 最晚可何时发生) |
| e(k) | 活动 k 的最早开始时间 | 活动 k 对应边<i,j>,e(k)=Ve(i)(事件 i 最早发生时,活动 k 可开始) |
| l(k) | 活动 k 的最晚开始时间 | 活动 k 对应边 `,l(k)=Vl(j)−活动k的权重(事件 j 最晚发生前,活动 k 需完成,故用 Vl(j) 减活动时长) |
| l(k)−e(k) | 活动 k 的余量时间 | 若值为 0:活动 k 是关键活动(无余量,延误即影响工期);若值 > 0:非关键活动(有缓冲时间,可适当延误) |
(4)关键路径计算步骤(实例解析)
以讲课中 “11 项活动、9 个事件” 的 AOE 网为例(原点 V1,汇点 V9,边权重为活动时长,如 A1=V1→V2,时长 6 天),计算步骤如下:
步骤 1:计算所有事件的最早发生时间Ve(i)(从原点 V1开始,正向推导)
-
原点规则:Ve(1)=0(起始事件最早 0 时刻发生);
-
正向推导:对每个顶点 j,Ve(j)=max{Ve(i)+边的权重}(取所有前驱路径的最长值):
-
Ve(2)=Ve(1)+A1权重=0+6=6;
-
Ve(3)=Ve(1)+A2权重=0+4=4;
-
Ve(4)=Ve(2)+A3权重=6+5=11;
-
Ve(5)=max{Ve(2)+A4=6+3=9,Ve(3)+A*5=4+5=9}=9;
-
以此类推,直到计算出汇点V**E(9)=20(即工程最短完成时间为 20 天)。
-
步骤 2:计算所有事件的最晚发生时间Vl(i)(从汇点 V9 开始,反向推导)
-
汇点规则:Vl(9)=Ve(9)=20(结束事件最晚发生时间 = 最早发生时间,否则工期延误);
-
反向推导:对每个顶点 i,Vl(i)=min{Vl(j)−边}(取所有后继路径的最小值,不影响总工期):
-
Vl(8)=Vl(9)−A11权重=20−2=18;
-
Vl(7)=Vl(8)−A10权重=18−4=14;
-
Vl(6)=min{Vl(7)−A8=14−3=11,Vl(8)−A9=18−5=13}=11;
-
以此类推,直到计算出所有顶点的Vl(i)。
步骤 3:计算所有活动的e(k)、l(k)及余量时间
-
对每个活动 k(对应边 `:
-
e(k)=Ve(i),l(k)=Vl(j)−边<i,j>权重;
-
举例:活动 A1(V1→V2,权重 6):e(A1)=Vl(1)=0,l(A1)=Vl(2)−6=6−6=0,余量 = 0→关键活动;
-
活动 A5(V3→V5,权重 5):e(A5)=Ve(3)=4,l(A5)=Vl(5)−5=9−5=4,余量 = 0→关键活动;
-
活动 A6(V3→V6,权重 2):e(A6)=Ve(3)=4,l(A6)=Vl(6)−2=11−2=9,余量 = 5→非关键活动(可延误 5 天)。
-
步骤 4:确定关键路径
- 关键活动(余量 = 0)连成的路径,即关键路径。本例中关键路径为:V1→V2→V5→V7→V8→V9(对应活动 A1→A4→A9→A10→A11),该路径总时长 = 20 天(与工程最短完成时间一致)。
(5)扩展应用与优化思路
- 应用场景:项目管理(如软件开发 “需求分析→编码→测试”)、生产调度(如汽车生产 “零件加工→组装→质检”);
- 优化思路:缩短关键路径的总时长,可通过压缩关键活动的时长(如 “打地基从 10 天压缩到 8 天”);非关键活动的余量时间可用于资源调配(如将非关键活动的工人调去支援关键活动)。
- 注意事项:若关键路径有多条(如工程有 2 条关键路径,总时长均 20 天),需同时监控所有关键路径,任一关键路径延误都会导致工期延误。
参考资料:教材《数据结构 C 语言 第 3 版》 数据结构考研指导(基础篇) 、数据结构考研指导(基础篇) 视频课程|赵海英

浙公网安备 33010602011771号