【王道考研·数据结构】第六章 图(整章完整版)

【王道考研·数据结构】第六章 图(整章完整版)
Austoin引言
第六章是数据结构里最核心的章节之一,因为它把线性关系和层次关系进一步扩展到了更一般的网状关系。图是一种比树更普遍的结构,可以用来描述城市交通、社交网络、网页链接、工程任务等各种复杂关系。
本章可以整理成四条主线:
- 图的基本概念与存储结构(邻接矩阵、邻接表、十字链表、邻接多重表)
- 图的遍历(BFS、DFS)
- 最小生成树与最短路径(Prim、Kruskal、Dijkstra、Floyd)
- 拓扑排序与关键路径(AOV、AOE网络)
本篇会把第六章所有 PPT 内容合并成一篇完整文章,并且把 PPT 中只给结论、图示但没有讲透的地方一并补全。
图像化理解(Mermaid)
mindmap
root((第六章主线))
图的表示
邻接矩阵(稠密图)
邻接表(稀疏图)
十字链表(有向图)
邻接多重表(无向图)
图的算法
遍历: BFS / DFS
生成树: Prim / Kruskal
最短路: BFS / Dijkstra / Floyd
DAG: 拓扑排序 / 关键路径
一句话:先学图怎么存,再学图上怎么跑算法。
一、图的基本概念
1. 图的定义
图 G 由两个集合 V 和 E 组成,记作 G = (V, E):
- V:顶点集(Vertex),非空有限集合
- E:边集(Edge),有限集合
1 | V(G) = {v1, v2, v3, v4} |
1 | v1 -------- v2 |
2. 有向图与无向图
无向图
- 边用无序对 (v, w) 表示
- (v, w) 和 (w, v) 是同一条边
- 无向图边数上界:n(n-1)/2
有向图
- 边用有序对 <v, w> 表示
- <v, w> 表示从 v 指向 w 的弧
- 有向图边数上界:n(n-1)
1 | 无向图 有向图 |
3. 顶点的度
无向图
- 度:与该顶点相连的边的数目
-握手定理:所有顶点的度之和 = 2 × 边数
有向图
- 出度:从该顶点出发的弧的数目
- 入度:指向该顶点的弧的数目
- 度 = 出度 + 入度
- 握手定理:所有顶点的出度之和 = 所有顶点的入度之和 = 边数
4. 简单图与多重图
- 简单图:不存在重复边,不存在自环
- 多重图:存在重复边
考研中默认讨论的都是简单图。
5. 路径、回路、环
- 路径:从一个顶点到另一个顶点所经过的顶点序列
- 路径长度:路径上边的数目
- 回路(环):起点和终点相同的路径
- 简单路径:顶点不重复的路径
- 简单回路:顶点不重复的回路
6. 连通图与强连通图
无向图
- 连通:两个顶点之间存在路径
- 连通图:任意两个顶点都连通的图
- 连通分量:无向图中的极大连通子图
有向图
- 强连通:两个顶点之间存在双向路径
- 强连通图:任意两个顶点都强连通
- 强连通分量:有向图中的极大强连通子图
1 | 连通图示例: 非连通图示例: |
7. 生成树与生成森林
- 生成树:连通图的极小连通子图(包含所有顶点、n-1条边)
- 生成森林:非连通图各连通分量的生成树组成
8. 权值与网
- 权值:边上带的数值(距离、成本等)
- 网:带权的图(有权图)
二、图的存储结构
1. 邻接矩阵法
存储方式
用一个 n×n 的二维数组存储:
1 | | 0 1 2 3 (列号) |
- A[i][j] = 1(或权值):表示顶点 i 到 j 有边
- A[i][j] = 0(或∞):表示无边
代码实现
1 |
|
复杂度分析
| 操作 | 时间复杂度 |
|---|---|
| 判断两顶点是否相邻 | O(1) |
| 获取某顶点的度 | O(n) |
| 空间复杂度 | O(n²) |
特点
- 适合稠密图
- 空间复杂度高 O(n²)
- 矩阵A的i行/j列非零元素个数 = 对应顶点的度(无向图)
2. 邻接表法
存储方式
每个顶点建立一个单链表,存储与其相邻的所有顶点。
1 | v0 → [v1] → [v2] |
代码实现
1 |
|
复杂度分析
| 操作 | 时间复杂度 |
|---|---|
| 判断两顶点是否相邻 | O(degree(v)) |
| 获取某顶点的度 | O(degree(v)) |
| 空间复杂度 | O(V + E) |
特点
- 适合稀疏图
- 节省空间
- 无向图边表结点数 = 2E,有向图边表结点数 = E
3. 十字链表(有向图)
同时存储出边和入边,解决邻接表无法同时获取入度和出度的问题。
1 | 十字链表结构示意: |
适用于需要频繁获取入度的有向图算法(如拓扑排序)。
4. 邻接多重表(无向图)
每条边只存储一次,同时关联两个顶点,解决邻接表中删边需要删除两个结点的问题。
1 | 邻接多重表结构示意: |
适用于需要频繁删边的无向图算法(如割点、桥的判定)。
5. 四种存储结构对比
| 存储结构 | 空间复杂度 | 适用场景 | 特点 |
|---|---|---|---|
| 邻接矩阵 | O(V²) | 稠密图 | 判断相邻 O(1),度获取 O(V) |
| 邻接表 | O(V+E) | 稀疏图 | 空间省,判断相邻 O(degree) |
| 十字链表 | O(V+E) | 有向图 | 兼顾出入度 |
| 邻接多重表 | O(V+E) | 无向图 | 删边高效 |
三、图的基本操作
1 | // 图的基本操作(邻接矩阵实现) |
四、图的遍历
1. 广度优先遍历(BFS)
算法思想
层序遍历:先访问起始顶点,再访问其所有邻接点,然后访问邻接点的邻接点……
1 | BFS层序遍历示例: |
代码实现(邻接表)
1 |
|
复杂度分析
| 存储结构 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| 邻接矩阵 | O(V²) | O(V) |
| 邻接表 | O(V + E) | O(V) |
BFS 生成树
1 | 原图: BFS生成树: |
2. 深度优先遍历(DFS)
算法思想
类似树的先序遍历:尽可能深地访问顶点,走不通再回溯。
代码实现(邻接表)
1 |
|
复杂度分析
| 存储结构 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| 邻接矩阵 | O(V²) | O(V) |
| 邻接表 | O(V + E) | O(V) |
DFS 生成树/森林
1 | 原图: DFS生成树: |
3. BFS 与 DFS 对比
| 特性 | BFS | DFS |
|---|---|---|
| 数据结构 | 队列 | 栈(或递归) |
| 生成树 | 宽而短 | 窄而深 |
| 适用场景 | 最短路径、层次遍历 | 拓扑排序、连通分量 |
| 空间复杂度 | O(V) | O(V) |
五、最小生成树
1. 基本概念
- 生成树:包含图中所有顶点的极小连通子图
- 最小生成树(MST):权值之和最小的生成树
MST 性质
切分定理:给定任意切分,横切该切分的所有边中,权重最小的边必然属于某个最小生成树。
1 | 切分示意图: |
2. Prim 算法
算法思想
从某个顶点开始,逐步加入顶点,每次选择与当前生成树距离最短的顶点。
1 | Prim算法过程示例: |
代码实现
1 | void Prim(MGraph G) { |
复杂度分析
| 时间复杂度 | 适用场景 |
|---|---|
| O(V²) | 稠密图 |
3. Kruskal 算法
算法思想
每次选择权值最小且不构成环的边,逐步构建生成树。
1 | Kruskal算法过程示例: |
代码实现
1 | // 并查集实现 |
复杂度分析
| 时间复杂度 | 适用场景 |
|---|---|
| O(E log E) | 稀疏图 |
4. Prim vs Kruskal
| 算法 | 思想 | 时间复杂度 | 适用场景 |
|---|---|---|---|
| Prim | 逐点加入 | O(V²) | 稠密图 |
| Kruskal | 逐边加入 | O(E log E) | 稀疏图 |
六、最短路径
1. BFS 算法(无权图)
适用场景
所有边权值相同的最短路径。
代码实现
1 | void BFS_MIN_Distance(ALGraph G, int v) { |
复杂度
O(V + E)
2. Dijkstra 算法
适用场景
单源最短路径,且所有边权值非负。
算法思想
类似 Prim,逐步加入距离源点最近的顶点。
代码实现
1 | void Dijkstra(MGraph G, int v0) { |
复杂度分析
| 存储结构 | 时间复杂度 |
|---|---|
| 邻接矩阵 | O(V²) |
| 邻接表 + 堆 | O((V+E) log V) |
⚠️ 重要限制
Dijkstra 算法不能处理负权边!
1 | 负权边示例导致的问题: |
3. Floyd 算法
适用场景
多源最短路径(任意两点间最短路径)。
算法思想
动态规划:允许通过中间顶点中转。
1 | Floyd核心思想: |
代码实现
1 | void Floyd(MGraph G) { |
复杂度分析
O(V³),适合小规模图
4. 三种最短路径算法对比
| 算法 | 作用 | 时间复杂度 | 限制 |
|---|---|---|---|
| BFS | 单源无权最短路径 | O(V+E) | 边权相同 |
| Dijkstra | 单源非负权最短路径 | O(V²) 或 O(E log V) | 不能有负权 |
| Floyd | 多源最短路径 | O(V³) | 不能有负环 |
七、有向无环图描述表达式
1. 基本概念
- DAG:有向无环图(Directed Acyclic Graph)
- 可用于表示算术表达式,避免重复计算
2. 表达式 DAG 构造
1 | 表达式:(a + b) * (a + b) + c |
3. 构造方法
按照运算符优先级建树,然后合并相同子表达式。
八、拓扑排序
1. AOV 网
- AOV:Activity On Vertex(用顶点表示活动)
- 顶点表示活动,边表示活动间的优先关系
- 必须是 DAG
2. 拓扑排序算法
算法思想
每次选择入度为 0 的顶点输出,然后删除该顶点及其出边。
1 | bool TopologicalSort(ALGraph G) { |
复杂度
O(V + E)
3. 逆拓扑排序
每次选择出度为 0 的顶点输出。
1 | // 逆拓扑排序(邻接表) |
4. 拓扑排序唯一性
若图中只有一个入度为 0 的顶点,则拓扑排序唯一。
九、关键路径
1. AOE 网
- AOE:Activity On Edge(用边表示活动)
- 顶点表示事件(开始/结束),边表示活动
- 通常用于工程计划
1 | AOE网示例: |
2. 关键路径概念
- 事件最早发生时间 ve(k):从起点到 vk 的最长路径长度
- 事件最晚发生时间 vl(k):在不影响整个工程工期的前提下,事件 vk 允许的最晚发生时间
- 活动最早开始时间 e(i):活动 ai 的起点事件的最早发生时间
- 活动最晚开始时间 l(i):活动 ai 的终点事件的最晚发生时间减去 ai 的持续时间
- 关键活动:e(i) = l(i) 的活动
- 关键路径:由关键活动组成的路径
3. ve/vl 计算公式
ve(最早发生时间)
1 | ve(源点) = 0 |
vl(最晚发生时间)
1 | vl(汇点) = ve(汇点) |
4. e/l 计算与关键活动
1 | e(i) = ve(活动起点) |
5. 代码实现
1 | void CriticalPath(ALGraph G) { |
十、本章最容易写错、算错、混淆的地方
- 邻接矩阵的空间复杂度是 O(V²),不是 O(E)
- 邻接表的空间复杂度是 O(V+E)
- 十字链表用于有向图,邻接多重表用于无向图
- BFS/DFS 的时间复杂度:邻接矩阵 O(V²),邻接表 O(V+E)
- Prim 算法类似 Dijkstra,但比较的是到集合的距离,不是到源点的距离
- Kruskal 需要用并查集判断是否成环
- Dijkstra 不能处理负权边!
- Floyd 可以处理负权边,但不能有负环
- 拓扑排序检查环:输出顶点数 < 总顶点数则存在环
- 拓扑排序唯一性:只有当入度为0的顶点唯一时,唯一
- 关键路径是所有路径中长度最长的,不是最短的
- 事件最早用 max(取最长),事件最晚用 min(取最短)
- 活动最早等于起点事件的 ve,活动最晚等于终点事件的 vl 减持续时间
- 缩短关键活动可以缩短整个工期,但缩短到一定程度后可能产生新的关键路径
十一、图像化总览(Mermaid)
1. BFS 与 DFS 访问风格图
flowchart LR
BFS[BFS 按层扩散] --> B1[起点]
B1 --> B2[第一层邻接点]
B2 --> B3[第二层邻接点]
DFS[DFS 一路到底再回溯] --> D1[起点]
D1 --> D2[邻接点]
D2 --> D3[更深邻接点]
D3 --> D4[回退]
2. Prim 与 Kruskal 选边风格图
flowchart TD
P[Prim] --> P1[维护点集合]
P1 --> P2[选连接集合内外的最小边]
K[Kruskal] --> K1[维护边集合]
K1 --> K2[按边权从小到大选边]
K2 --> K3[不成环则加入]
3. 三类最短路定位图
flowchart TD
S[最短路问题] --> U[无权单源 -> BFS]
S --> N[有权单源非负 -> Dijkstra]
S --> M[多源最短路 -> Floyd]
4. 拓扑排序判环图
flowchart TD
A[持续删除入度为0顶点] --> B{输出顶点数是否等于 V}
B -->|是| C[无环 DAG]
B -->|否| D[有环]
5. 关键路径计算链图
flowchart LR
A[拓扑序] --> B[求 ve 最早事件]
B --> C[逆拓扑序]
C --> D[求 vl 最晚事件]
D --> E[计算 e/l]
E --> F[e==l 为关键活动]
十二、易错点与易混淆点(详细版)
A. 易混淆点(A vs B)
连通图 vs 强连通图
- 连通图:无向图里任意两点可达
- 强连通图:有向图里任意两点双向可达
邻接矩阵 vs 邻接表
- 邻接矩阵判断相邻
O(1),空间O(V^2) - 邻接表空间
O(V+E),更适合稀疏图
- 邻接矩阵判断相邻
Prim vs Dijkstra
- Prim 比较“到生成树集合的最小代价”
- Dijkstra 比较“到源点的最短距离”
- 两者流程像,但优化目标不同。
AOV vs AOE
- AOV:顶点是活动,做拓扑排序
- AOE:边是活动,做关键路径
最短路径 vs 关键路径
- 最短路径求“最小总代价”
- 关键路径求“最大总工期(最长路径)”
B. 易错点(为什么错 + 怎么改)
把 Dijkstra 用在负权图
- 错因:忽略“已确定最短路不会再变短”这个前提。
- 正解:Dijkstra 仅适用于非负权边。
Kruskal 漏做成环检查
- 错因:只按边权排序一路加入。
- 正解:每加一条边前用并查集判断是否同根。
拓扑排序输出一个序列就当 DAG
- 错因:没核对输出顶点个数。
- 正解:必须检查
count == V才能判无环。
关键路径 ve/vl 用反了 max/min
- 错因:把两套递推都写成 max 或都写成 min。
- 正解:
ve前推取maxvl回推取min
邻接矩阵复杂度按 E 写
- 错因:被“边数少”误导。
- 正解:矩阵遍历通常是按顶点双循环,常见
O(V^2)。
C. 本章做题顺序建议
1 | 先判图类型(有向/无向、有权/无权、稠密/稀疏) |
十三、书签级思维导图复现(第六章,Mermaid)
mindmap
root((第6章 图))
6.1 图的基本概念
有向/无向
度、入度、出度
路径、回路、连通
生成树、网
6.2 图的存储结构
邻接矩阵
邻接表
十字链表(有向图)
邻接多重表(无向图)
6.3 图的遍历
BFS(队列)
DFS(递归/栈)
6.4 生成树与最短路
Prim / Kruskal
Dijkstra / Floyd
算法适用条件
6.5 DAG 与工程网络
拓扑排序
AOV 网络
AOE 关键路径
十四、PDF 例题与知识点补充(图)
例题 1:无向图握手定理
题目:无向图中顶点度数和与边数关系是什么?
答案:所有顶点度数和 = 2|E|。
详细解析:
- 每条无向边都连接两个端点。
- 统计度数时,每条边会被计算两次。
例题 2:有向图度关系
题目:有向图中入度和、出度和与边数关系是什么?
答案:Σ入度 = Σ出度 = |E|。
详细解析:
- 每条有向边都有且仅有一个起点和一个终点。
- 因此每条边分别贡献一次出度和一次入度。
例题 3:连通分量与生成森林
题目:非连通无向图做 DFS/BFS 会得到什么结构?
答案:遍历生成森林。
详细解析:
- 每个连通分量各自产生一棵生成树。
- 所有分量生成树合在一起就是生成森林。
例题 4:邻接矩阵复杂度
题目:邻接矩阵判相邻和空间复杂度分别是什么?
答案:判相邻 O(1),空间 O(V^2)。
详细解析:
- 判相邻是直接访问矩阵元素。
- 需要开
V*V个单元存边信息。
例题 5:邻接表复杂度
题目:邻接表空间复杂度和适用场景是什么?
答案:空间 O(V+E),更适合稀疏图。
详细解析:
- 邻接表只存实际存在的边。
- 当
E << V^2时节省显著。
例题 6:BFS 最短路适用条件
题目:BFS 可用于哪类最短路径问题?
答案:无权图(或等权图)单源最短路。
详细解析:
- BFS 按层扩展,层数即边数。
- 在等权边场景中,最先到达即最短。
例题 7:DFS 与 BFS 差异
题目:DFS 与 BFS 的核心访问差异是什么?
答案:BFS 按层,DFS 先深后回溯。
详细解析:
- BFS 依赖队列,体现 FIFO。
- DFS 依赖递归栈或显式栈,体现深度优先。
例题 8:Prim 与 Kruskal 选型
题目:Prim 与 Kruskal 各适合什么图?
答案:Prim 常用于稠密图,Kruskal 常用于稀疏图。
详细解析:
- Prim 以点集扩张,常配邻接矩阵。
- Kruskal 以边为中心,排序后并查集判环。
例题 9:Dijkstra 限制
题目:Dijkstra 的关键限制是什么?
答案:不能处理负权边。
详细解析:
- Dijkstra 依赖“已确定最短路不会再变短”的性质。
- 负权边会破坏该性质,导致错误。
例题 10:Floyd 递推式
题目:Floyd 更新公式是什么?
答案:d[i][j] = min(d[i][j], d[i][k] + d[k][j])。
详细解析:
- 含义是“走中转点
k是否更短”。 - 三重循环逐步放宽可用中转点集合。
例题 11:拓扑排序判环
题目:如何通过拓扑排序判定有向图是否有环?
答案:若最终输出顶点数 < V,则有环。
详细解析:
- 有环时环上顶点入度无法降到 0。
- 因而这些点无法被输出,计数不足
V。
例题 12:拓扑序唯一性
题目:什么时候拓扑序唯一?
答案:每一步入度为 0 的可选顶点始终唯一。
详细解析:
- 若某一步出现多个入度 0 顶点,就可产生不同选择顺序。
- 只有全程唯一选择才唯一。
例题 13:关键路径核心量
题目:关键路径计算中 ve 和 vl 分别怎么求?
答案:
ve前推取maxvl回推取min
详细解析:
ve表示事件最早发生时间,必须满足所有前驱约束,取最大。vl表示不延误工期的最晚时间,回推取最小。
例题 14:关键活动判定
题目:活动何时为关键活动?
答案:当 e(i) == l(i)。
详细解析:
e(i)是活动最早开始时间。l(i)是不延误工期的最晚开始时间。- 两者相等说明无机动时间,必在关键路径上。
例题 15:关键路径本质
题目:AOE 网关键路径本质是什么?
答案:决定总工期的最长路径。
详细解析:
- 工程总工期由最慢那条依赖链决定。
- 缩短非关键活动可能不改变总工期。
十五、第六章必背核对表
- 邻接矩阵/邻接表复杂度要会对比。
- BFS/DFS 的数据结构支撑不能写反。
- MST 两算法:Prim(点扩张)、Kruskal(边筛选)。
- Dijkstra 负权限制必须写明。
- Floyd 是多源最短路。
- 拓扑排序可用于判环。
- 关键路径计算顺序:拓扑求
ve-> 逆拓扑求vl-> 判关键活动。
总结
第六章虽然内容多,但主线清晰:
- 存储结构:邻接矩阵(稠密)、邻接表(稀疏)
- 遍历:BFS(最短路径)、DFS(递归)
- 最小生成树:Prim(稠密)、Kruskal(稀疏)
- 最短路径:BFS(无权)、Dijkstra(非负权)、Floyd(多源)
- 拓扑排序:AOV 网、检测环
- 关键路径:AOE 网、ve/vl/e/l
必须滚瓜烂熟的内容:
- 四种存储结构的复杂度对比
- BFS 与 DFS 的区别和适用场景
- Prim 与 Kruskal 的选择依据
- Dijkstra 的负权限制
- 拓扑排序的环检测原理
- 关键路径的 ve/vl 计算方法
把这些吃透,图这一章就能达到考研要求。
作者:[Austoin]
参考教材:王道考研《数据结构》













