数据结构 06|图:度怎么数、拓扑序列怎么列、Dijkstra 与关键路径怎么手算

2013 年第 7 题,我对着一个 4×4 的邻接矩阵数了半天,选了 A。

题目给的矩阵长这样:

复制代码
     0  0  0  1
A =  1  0  1  0
     0  1  0  0
     1  1  0  0

问各顶点的度依次是多少。我想的是"度 = 这一行里非零元素的个数",数出来 1, 2, 1, 2,选 A,很干脆。

答案是 C:3, 4, 2, 3。

问题出在我没先看矩阵对不对称 。这个矩阵第 1 行第 4 列是 1,第 4 行第 1 列也是 1,但第 1 行第 2 列是 0、第 2 行第 1 列却是 1------不对称 ,说明它是有向图 。有向图里"度 = 入度 + 出度",得把行和列加起来数,只数行就漏了一半。

从那以后我做邻接矩阵的题,第一件事就是看它是不是对称的。这一步花两秒,能救一整道题。


〇、先花两分钟:图、度、连通是什么

图 (Graph):一堆顶点 (vertex,也叫结点)和一堆连接它们的边 (edge)。边没有方向叫无向图 ,边有方向(写成 <v₁, v₂>,只能从 v₁ 走到 v₂)叫有向图 ,也叫弧。

度:连在这个顶点上的边有几条。

  • 无向图:度 = 挨着它的边数

  • 有向图:入度 (箭头指向它的)+ 出度(从它指出去的)

连通 (无向图):从任意一个顶点都能走到任意另一个顶点。强连通 (有向图):从 u 到 v、从 v 到 u 双向都能走。

生成树 :连通图的一棵"骨架"------包含全部顶点 ,但只保留 n−1 条边且不成环。带权图里权值总和最小的那棵叫最小生成树。

度的两种数法、四种存储结构、BFS/DFS 对比、拓扑与关键路径手算


一、度的计算与图的基本概念

1.1 无向图 vs 有向图,度怎么数

图类型 度的算法
无向图 邻接矩阵中该顶点对应一行的非零元素个数
有向图 该顶点行的非零数(出度)+ 列的非零数(入度)

2013(7) 完整验算一遍:

顶点 行(出度) 列(入度) 度
1 0 0 0 1 → 1 0,1,0,1 → 2 3
2 1 0 1 0 → 2 0,0,1,1 → 2 4
3 0 1 0 0 → 1 0,1,0,0 → 1 2
4 1 1 0 0 → 2 1,0,0,0 → 1 3

2013(7) 答案:C(3, 4, 2, 3)。

1.2 握手定理:度之和 = 2 × 边数

每条边在它的两个端点上各被数一次,所以:

复制代码
无向图:Σ 各顶点度 = 2|E|
有向图:Σ 入度 = Σ 出度 = |E|

2009(7) 考的是无向连通图的性质:

  • I. 所有顶点的度之和为偶数 → 对(等于 2|E|)

  • II. 边数大于顶点数减 1 → 错,树就是 |E| = |V| − 1

  • III. 至少有一个顶点的度为 1 → 错,n ≥ 3 的完全图每个顶点度都是 n−1 ≥ 2

2009(7) 答案:A(只有 I)。

2017(7) 是握手定理的代数应用:16 条边,度为 4 的顶点 3 个、度为 3 的 4 个、其余顶点度都小于 3(即最大为 2),求顶点数至少多少。

设其余顶点数为 x,且都取最大度 2(这样用的顶点最少):

复制代码
4×3 + 3×4 + 2x = 16×2
24 + 2x = 32
x = 4

顶点数 = 3 + 4 + 4 = 11。

2017(7) 答案:B(11)。

(思路:要让顶点数最少,就让剩余的度尽量"摊"到少数顶点上,所以其余顶点的度都取允许的最大值 2。)

1.3 连通性:保证连通需要多少条边

2010(7) :7 个顶点,在任何情况下都连通,最少需要多少条边?

关键词是"在任何情况下"------不仅现在连通,随便你怎么挪动边都不能把它拆开。做法是:让任意 6 个顶点构成完全图,再把第 7 个顶点接上去。

复制代码
C(6,2) = 6×5/2 = 15 条(6 个点两两相连)
再 + 1 条(把第 7 个点连进来)
= 16 条

为什么这样最省?反过想:如果边数 ≤ 15,就可能存在一个"孤立"的划分方式把图拆成两半。

2010(7) 答案:C(16)。

对照 2022(6) :问 |V| 和 |E| 满足什么条件时图一定不连通。答案是 D(|V| > |E| + 1)。理由:n 个顶点要连通至少需要 n−1 条边,所以 |E| < |V| − 1 时必定不连通,移项就是 |V| > |E| + 1。

2025(6) 是概念判断题,答案 C :各顶点度都 ≥ 2 的无向图必有回路。其余三项:A 错(有向环里每个顶点入度都是 1,没有入度为 0 的);B 错(拓扑序列可以多个);D 错(带权图最短路用 Dijkstra,BFS 只能处理边权都为 1 的情况)。

2011(8) :答案 C(仅 III)。I 错(回路首尾相接,顶点重复了,不是简单路径);II 错(稀疏图用邻接表省空间,邻接矩阵是 O(n²));III 对(有回路就没有拓扑序列)。


二、存储结构:邻接矩阵、邻接表、邻接多重表

存储 空间 适合场景 求度
邻接矩阵 O(n²) 稠密图 无向看行,有向行+列
邻接表 O(n + e) 稀疏图 有向图出度好求、入度要扫全表
邻接多重表 --- 无向图(每条边只存一次) 数该顶点在边链表中出现几次
十字链表 --- 有向图 入度出度都好求

2012(5) :n 个顶点 e 条边、邻接表存储的有向图 ,BFS 的时间复杂度 → O(n + e)。答案 C。 记忆法:每个顶点入队一次是 O(n),每条出边被扫一次是 O(e)。

2016(7) :拓扑排序用邻接表存储,时间复杂度 → O(n + e)。答案 B。同一个结论。

2024(4) 考的是邻接多重表求度:题目给了无向图的邻接多重表,问顶点 b、d 的度。答案是 B(2, 4)。

方法:邻接多重表里一条边只对应一个边结点 ,这个结点会同时挂在这条边两个端点 的链表上。所以求某个顶点的度,就是数它在所有边链表中出现了几次。

⚠️ 说明一下:这道题的原图在题库里是缺失的,上面给的是官方答案和通用做法,没有还原具体边表。遇到同类题按"数出现次数"的方法做即可。


三、图的遍历:BFS 与 DFS

BFS(广度优先) :用队列。从一个顶点出发,先访问它所有未访问过的邻接点,再依次从这些邻接点往外扩一层。像水波一圈圈扩散。

DFS(深度优先) :用栈(或递归)。一条路走到黑,走不动了就回退一步换条路。

3.1 判定一个序列是不是 BFS/DFS 序列

这类题年年考,做法只有一个:拿选项去模拟。

2013(8) 问哪个不是 BFS 序列。判据是 BFS 的一条硬性质:

若 v 在序列中排在 u 前面,且 u、v 之间有边,那么 u 的所有未访问邻接点 必须在 v 的所有未访问邻接点之前或同层 被访问------换句话说,BFS 序列里不会"跳层回退"。

题库里给的答案是 D 。题目原图同样是缺失的,但这类题的通用判据可以用:DFS 序列经常出现"沿一条链一路走到底"的形态,BFS 序列则是一层一层铺开 。选项 D 的 a, b, c, d, h, e, f, g 是典型的一路深入,所以判定它不是 BFS。

2016(6) 同理,问哪个不是 DFS 序列,答案 D 。判据反过来:DFS 走到某个顶点后,下一个必须是它尚未访问的邻接点之一;如果选项里出现了"当前顶点的邻接点还没访问完就跳到别处",那就不可能是 DFS。

⚠️ 再次提醒:这两道题的原图在题库里都没有,上面给的是官方答案与判定原则,不是逐图推演。

3.2 DFS 序列能有多少个

2015(5) :有向图 V = {v₀, v₁, v₂, v₃},边集 <v₀,v₁>, <v₀,v₂>, <v₀,v₃>, <v₁,v₃>,从 v₀ 开始 DFS,可能的不同遍历序列个数是?

边列出清楚:v₀ → v₁、v₀ → v₂、v₀ → v₃、v₁ → v₃。

从 v₀ 出发,第一步可以走 v₁、v₂、v₃ 中的任意一个,逐个展开:

  • 先走 v₁ → v₁ 只能去 v₃ → 访问 v₃ → 回退,还剩 v₂ → v₀,v₁,v₃,v₂

  • 先走 v₂ → v₂ 没有出边 → 回退,剩下 v₁、v₃ 可选

    • v₀,v₂,v₁,v₃

    • v₀,v₂,v₃,v₁(走 v₃ 时,v₃ 无出边,再走 v₁)

  • 先走 v₃ → 无出边 → 回退,剩下 v₁、v₂

    • v₀,v₃,v₁,v₂

    • v₀,v₃,v₂,v₁

共 5 种。2015(5) 答案:D。

3.3 DFS 与拓扑排序的关系

2020(6) 是道很有意思的题:把递归 DFS 里"访问顶点"的语句移到退出递归前(也就是后序打印),输出的结果是什么?

答案 B:逆拓扑有序序列。

道理:DFS 递归时,先访问的顶点先被压进栈底。若存在边 <vᵢ, vⱼ>,那么 vᵢ 一定比 vⱼ 先入栈,也就比 vⱼ 后出栈 、后打印。拓扑序列要求 vᵢ 在 vⱼ 之前,反过来打印自然就是逆拓扑。

(顺带记:DFS 逆序打印 = 逆拓扑排序,这是一个常考的等价关系。)

3.4 BFS 也能求最短路径

2023(6) :无向连通图各边权都是 1,哪个算法一定能求出单源最短路径?答案是 B(仅 III,BFS)。

因为边权都为 1 时,"路径长度"就等于"经过的边数",而 BFS 天然按层扩展,第一次到达某顶点的层数就是最短距离。Prim 和 Kruskal 求的是最小生成树,不是最短路径------这两个概念每年都有人混。


四、最小生成树:Prim 与 Kruskal

Prim :从一个顶点开始,每次挑一条连接"已选集合"和"未选顶点"的权值最小的边,把新顶点拉进来。像滚雪球。

Kruskal :把所有边按权值从小到大排序,依次尝试加入,只要不成环就留下,直到选够 n−1 条。像挑便宜货。

4.1 概念判断题(2012(8))

  • Ⅰ. 最小生成树的代价唯一 → 对。树形可以不同(权值相同的边导致),但总代价一定相同。

  • Ⅱ. 权值最小的边一定出现在所有最小生成树中 → 错。若权值最小的边有多条且构成环,那么某棵生成树里必然有一条被舍弃。

  • Ⅲ. Prim 从不同顶点出发得到的生成树一定相同 → 错。N 个点构成权值全相等的环时,从不同点出发会得到不同的树。

  • Ⅳ. Prim 和 Kruskal 得到的树总不相同 → 错。当最小生成树唯一(各边权互不相同)时,两者结果相同,"总不相同"太绝对。

2012(8) 答案:A(仅 Ⅰ)。

4.2 边的选择(2020(7))

Kruskal 的做法按权值升序扫边,用并查集判断是否成环。官方答案 A :(b,f)(b,d)(a,e)(c,e)(b,e)。

⚠️ 这道题的原图在题库里是缺失的,这里只给答案与算法步骤,不逐一还原边的权值。

4.3 Prim 与 Kruskal 选边的差别(2015(6))

题目问:哪条边可能是 Kruskal 第 2 次选中、但不是 Prim(从 V₄ 开始)第 2 次选中 的边。答案 C:(V₂, V₃)。

思路:

  • Kruskal 第 1 次一定选全局权值最小 的边,第 2 次选次小的------它是全局视角

  • Prim 从 V₄ 出发,第 1 次只能选与 V₄ 相连的最便宜边 ,第 2 次只能从"已选集合的邻边"里挑------它是局部视角

所以差别就出在"全局最小"和"从 V₄ 出发能碰到的最小"未必是同一条。这类题的通用做法:分别模拟两步,把两条边都列出来再比对。

⚠️ 原图同样缺失,这里给的是官方答案与通用做法。


五、拓扑排序

规则 :每次选一个入度为 0 的顶点输出,然后删掉它和它所有的出边;重复直到没有顶点。

判据 :如果某一步找不到入度为 0 的顶点,说明图里有环,拓扑排序不存在。

5.1 序列计数(2010(8))

2010(8) 问不同拓扑序列的个数,答案是 B(3) :abced、abecd、aebcd。

数个数的通用做法:画搜索树------每一步把当前所有入度为 0 的顶点都列出来,分别分支往下走,最后数叶子数。

⚠️ 原图缺失,这里给的是官方答案的三个序列。

5.2 邻接矩阵判拓扑序列(2012(6))

2012(6) :邻接矩阵主对角线以下元素全为 0,问拓扑序列如何?

对角线以下全 0 意味着:只有 i < j 时可能有边 i → j,绝不可能有 j → i------所以图中不可能有环 ,拓扑序列一定存在。

但是否唯一?举反例:3 个顶点的邻接矩阵

复制代码
0 1 1
0 0 0
0 0 0

顶点 1 指向 2 和 3,而 2、3 之间没有约束,拓扑序列可以是 1,2,3 也可以是 1,3,2,不唯一。

2012(6) 答案:C(存在,可能不唯一)。

5.3 判定某个序列是不是拓扑序列

做法:按序列顺序逐个检查,每个顶点被输出时,它的前驱必须已经全部输出过(或等价地:把它删掉后,下一个顶点的入度必须为 0)。

2014(7) 答案 D(3,1,4,2,6,5) 。解析的关键句是:"删掉 4 之后,顶点 2 和 6 的入度都为 0,此时选不同的顶点会得到不同序列"------所以答案落在以 3,1,4 开头的两项里。

2018(7) 问哪个不是 拓扑序列,答案 D(5,2,1,6,3,4) 。判定技巧很实用:看序列前两位 ------最初只有顶点 1 和 5 的入度为 0,所以前两位必然是 1,5 或 5,1。D 的第 2 位是 2,直接排除。

2021(7) 问拓扑序列个数,答案 A(1)------只有一种可能,说明每步都只有一个入度为 0 的顶点。

⚠️ 以上三题原图均缺失,给的是官方答案与判定方法。


六、最短路径:Dijkstra

算法步骤(背下来):

  1. 初始化:源点 dist = 0,其余 dist = ∞,集合 S = {}(已确定最短路径的点)

  2. 每趟从未确定 的顶点里选 dist 最小的那个,加入 S

  3. 用这个新加入的顶点去松弛 它的所有出边(若 dist[u] + w(u,v) < dist[v] 就更新)

  4. 重复 n−1 趟

关键性质 :Dijkstra 不能处理负权边。看到"负权"先想到 Bellman-Ford。

6.1 推演(2012(7))

题目说前两条最短路径的目标顶点依次是 b、c,问后续依次是什么。官方答案是 C(f, d, e)。

⚠️ 这道题题库里的推演表是 OCR 错位的(e 行和 f 行混在一起),我不照抄它。能确定下来的信息是:五个顶点被确定的顺序是

复制代码
b(2)  →  c(3)  →  f(4)  →  d(5)  →  e(6)

距离单调递增,符合 Dijkstra"每趟取当前最小"的性质。

提速技巧(解析里给了,很实用) :这种"问后续顺序"的题不用完整推演,用排除法更快。

  • A 说下一个是 d(经由 b,长度 5),但存在经由 c 到 f 的长度 4 的路径 ------ 4 < 5,d 不可能排在 f 前面,A 排除

  • B 同理(把 e 排在第 3 位),排除

  • 剩下 C、D 的差异在第 4、5 位(d、e 谁先):把 f 加入 S 后再松弛一次就能分辨

所以考场上这类题的正确打法是:先算前两趟确认自己没理解错,然后用"距离必须单调递增"去砍选项,比完整画表快得多。

6.2 dist 数组(2021(8))

2021(8) 问"求出第二条最短路径后,dist 中的内容"。答案是 C(21, 3, 14, 6)。

题库给的 dist 变化过程:

复制代码
dist[26, 3, ∞, 6]  →  [25, 3, ∞, 6]  →  [21, 3, 14, 6]
                        第 1 条后            第 2 条后

⚠️ 注意这里有个细节容易错:dist 数组里存的是"当前已知的最短距离",每加入一个新顶点就整体松弛一次,所以第 1 条路径确定后 26 变成 25,第 2 条后 25 变成 21。不是"确定一条才更新一个位置"。

2016(8) 答案 B(5, 2, 3, 6, 4)------从顶点 1 出发依次确定的目标顶点。⚠️ 原图缺失,只给答案。


七、关键路径:AOE 网

AOE 网 (Activity On Edge):用边表示活动 、顶点表示"事件"(某个活动完成、下个活动可以开始的时刻)的有向无环图。

两个核心量:

  • ve(i) :事件 i 的最早发生时间(从源点正向推,取 max)

  • vl(i) :事件 i 的最迟发生时间(从汇点反向推,取 min)

活动的两个量:

复制代码
活动 a = <i, j>,持续时间为 d
​
最早开始时间 e(a) = ve(i)
最迟开始时间 l(a) = vl(j) − d
时间余量 = l(a) − e(a)

关键活动 = 时间余量为 0 的活动 ;关键路径 = 全部由关键活动组成的路径 ,也就是从源点到汇点路径长度最长的那条。

7.1 概念(2020(8))

  • A. 关键路径是边数最多的路径 → 错 ,是权值之和最大

  • B. 关键路径是从原点到汇点路径长度最长的路径 → 对 ,答案 B

  • C. 增加任一关键活动时间不会延长工期 → 错,关键路径会变长

  • D. 缩短任一关键活动时间将会缩短工期 → 错 ,存在多条关键路径时,只缩短其中一条没用,另一条仍然决定工期

7.2 多关键路径(2013(9))

2013(9) 的关键结论很实用:题目算出的关键路径有三条------(b,d,c,g)、(b,d,e,h)、(b,f,h)。

要缩短工期,必须让每一条关键路径都变短 。所以正确选项里的活动必须覆盖所有关键路径:

  • A(c 和 e)→ 覆盖不到 (b,f,h) ✗

  • B(d 和 e)→ 覆盖不到 (b,f,h) ✗

  • C(f 和 d)→ d 覆盖前两条,f 覆盖第三条 ✓

  • D(f 和 h)→ h 只覆盖 (b,d,e,h)、(b,f,h),漏了 (b,d,c,g) ✗

2013(9) 答案:C。

7.3 时间余量计算(2022(7))

2022(7) 题库给了完整的 ve / vl 表,这是最值得照着练一遍的题:

结点 1 2 3 4 5 6
ve(i) 最早 0 2 5 8 9 12
vl(i) 最迟 0 4 5 8 11 12

时间余量公式:余量 = vl(终点) − ve(起点) − 活动持续时间

复制代码
c: vl(3) − ve(2) − 1 = 5 − 2 − 1 = 2
g: vl(6) − ve(3) − 1 = 12 − 5 − 1 = 6
h: vl(5) − ve(4) − 1 = 11 − 8 − 1 = 2
j: vl(6) − ve(5) − 1 = 12 − 9 − 1 = 2

2022(7) 答案:B(g,余量 6 最大)。

7.4 最早/最迟开始时间(2019(5))

2019(5) 问活动 d 的最早和最迟开始时间,答案 C(12 和 14)。

题库给的推演:d 必须等 a 和 b 都结束才能开始,所以最早开始 = 12;关键路径总长 27,结点 4 的最迟开始是 27 − 6 = 21,于是 d 的最迟开始 = 21 − 7 = 14。

⚠️ 原图缺失,这里给的是官方答案与计算口径。


八、用有向无环图表示表达式(2019(6))

2019(6) :表达式 (x+y)*((x+y)/x) 用有向无环图描述,最少需要几个顶点?答案 A(5)。

做法:

  1. 先把它画成一棵二叉树 :根是 *,左子树 x+y,右子树 (x+y)/x

  2. 数一下不同的东西:*、+、/、x、y → 重头戏是 (x+y) 出现了两次,可以共用同一个子树

  3. 同样两个 x 也能共用

去重之后顶点就是:*、+(同时服务左右两边)、/、x、y ------ 共 5 个。

(如果展开成树,那棵二叉树有 9 个顶点;DAG 的价值就在"公共子表达式只存一份"。)


九、时间复杂度汇总

算法 邻接矩阵 邻接表
BFS / DFS 遍历 O(n²) O(n + e)
拓扑排序 O(n²) O(n + e)
Prim O(n²) O(n log n)(堆优化)
Kruskal --- O(e log e)(排序占主导)
Dijkstra O(n²) O(n log n)(堆优化)

只需记牢两条(2012(5)、2016(7) 都考过):

  • 邻接表下,BFS / DFS / 拓扑排序 都是 O(n + e)

  • 邻接矩阵下是 O(n²)(因为要扫整个矩阵找邻接点)


十、速查表(文末收藏版)

度与边

项目 结论
无向图度 邻接矩阵该行非零个数
有向图度 出度(行)+ 入度(列)
握手定理 Σ度 = 2
n 点保证连通 C(n−1, 2) + 1 条边
一定不连通 |V| > |E| + 1

遍历与算法

项目 结论
BFS 队列,一层层铺开;边权全为 1 时可求最短路
DFS 栈/递归,一条路走到底
DFS 逆序打印 = 逆拓扑序列
邻接表复杂度 BFS / DFS / 拓扑都是 O(n + e)
Prim 从一点滚雪球,选"已选集合↔未选点"的最便宜边
Kruskal 边按权排序,不成环就加,选够 n−1 条
最小生成树 代价唯一,树形可能不唯一
Dijkstra 每趟选 dist 最小加入 S 再松弛;不能处理负权
拓扑排序 每步选入度 0 并删其出边;卡住 = 有环
关键路径 权值和最大的路径;多条时必须全部缩短才有效
时间余量 vl(终点) − ve(起点) − 活动时长

真题考频(2009---2025,共 31 道)

考点 年份(题号)
度的计算 / 握手定理 2009(7)、2013(7)、2017(7)
连通性与边数 2010(7)、2022(6)、2025(6)、2011(8)
存储结构与复杂度 2012(5)、2016(7)、2024(4)
BFS / DFS 遍历 2013(8)、2015(5)、2016(6)、2020(6)、2023(6)
最小生成树 2012(8)、2015(6)、2020(7)、2023(6)
拓扑排序 2010(8)、2012(6)、2014(7)、2018(7)、2021(7)
最短路径 2012(7)、2016(8)、2021(8)
关键路径 AOE 2013(9)、2019(5)、2020(8)、2022(7)
DAG 表示表达式 2019(6)

六个最容易错的地方

  1. 邻接矩阵先看成不对称------不对称就是有向图,度要行 + 列

  2. Prim / Kruskal 求的是最小生成树,不是最短路径

  3. 关键路径是"权值和最大"不是"边数最多"

  4. 多条关键路径时,只缩短一条不能缩短工期

  5. Dijkstra 每趟确定一个顶点后要整体松弛 dist,不是只改一个位置

  6. BFS 序列不回退、DFS 序列不跳层------判定题就靠这两句话

⚠️ 关于本篇的真题说明

图论题大量依赖原图,而 2010(8)、2013(8)、2014(7)、2015(6)、2016(6)、2016(8)、2018(7)、2019(5)、2020(7)、2021(7)、2021(8)、2022(7)、2024(4) 等题目的原图在题库中缺失或为 OCR 乱码 。这些题本文只提供官方答案与通用判定方法,不做图形还原------考场上请以试卷上的图为准,方法部分可以直接复用。

相关推荐
罗湖老棍子6 天前
【例题1】黑暗城堡(信息学奥赛一本通- P1486)
算法·图论·最短路·dijkstra·最小生成树·最短路径生成树
淡海水16 天前
02-04-原理篇-GC Roots与对象图遍历
算法·unity·c#·图·gc·遍历·roots
ysu_03142 个月前
13-图的基础:存储与表示
数据结构·图
2601_962381862 个月前
一文看懂爆火的 Graph Engineering, Loop Engineering、Agent
ai·图·循环·智能体·工程
positive_zpc2 个月前
进阶数据结构图——关键路径(四)
数据结构·图论·关键路径
positive_zpc2 个月前
进阶数据结构图——最短路径(二)
数据结构·算法·图论·最短路径
weixin_431600442 个月前
Agent Workflow 学习向:Code 节点,比模板更强的变量加工
后端·python·学习·ai·图
进哥AI研习社2 个月前
首屏加载链路拆解——从 TTFB 到 LCP 的关键路径优化
首屏加载·lcp·关键路径·ttfb·资源预加载·性能预算·瀑布分析
_Narcissus_2 个月前
动态规划初探(含完整代码实现)
c语言·数据结构·c++·算法·动态规划·背包问题·最短路径