复习位置:数据结构 -> 图
目标:用一篇文章串起图的存储、遍历、最小生成树、最短路径、拓扑排序和关键路径。
说明:为避免复制到 CSDN 后公式乱码,全文以普通文本、代码块和表格为主。
一、图这一章到底考什么
408中"图"这一章看起来算法很多,但实际上可以压缩成六类问题:
图怎么存
图怎么遍历
怎么连通所有顶点且代价最小
怎么找两点之间的最短路径
怎么判断任务先后顺序
怎么找工程中的关键活动
它们分别对应:
| 问题 | 典型知识点 |
|---|---|
| 图怎么存 | 邻接矩阵、邻接表 |
| 图怎么遍历 | DFS、BFS |
| 连通所有顶点且总代价最小 | 最小生成树:Prim、Kruskal |
| 求最短路 | Dijkstra、Floyd |
| 求任务执行顺序 | AOV网、拓扑排序 |
| 求工程最短完成时间 | AOE网、关键路径 |
复习时不要把这些算法混在一起。先判断题目在问哪一类问题,再选择对应算法。
二、图的基本概念
图由顶点集合和边集合组成,通常记为:
G = (V, E)
其中 V 表示顶点集合,E 表示边集合。
如果边没有方向,就是无向图;如果边有方向,就是有向图。如果边上带有长度、费用或时间,就是带权图。
1. 顶点的度
无向图中,一个顶点连接了多少条边,它的度就是多少。
所有顶点度数之和 = 2 × 边数
因为每条边会被两个端点各计算一次。
有向图中要区分:
入度:指向该顶点的边数
出度:从该顶点指出的边数
并且:
所有顶点入度之和 = 边数
所有顶点出度之和 = 边数
2. 路径与回路
路径是从一个顶点沿着边到达另一个顶点所经过的顶点序列。若路径起点和终点相同,就形成回路。
如果一个有向图中不存在有向回路,它就是:
有向无环图 DAG
拓扑排序和AOE网都建立在DAG上。
3. 连通与强连通
无向图中,任意两个顶点之间都存在路径,称为连通图。
有向图中,任意两个顶点之间都能相互到达,称为强连通图。
三、图的存储结构
1. 邻接矩阵
邻接矩阵用一个二维数组表示顶点之间是否有边。
对于无权图:
有边:1
无边:0
对于带权图:
有边:边权
无边:无穷大
对角线:0
邻接矩阵的特点是查询两点之间是否有边非常快,只需要访问一次数组,因此时间复杂度是 O(1)。但它始终需要 n × n 的空间,所以空间复杂度是 O(n²),更适合稠密图。
2. 邻接表
邻接表为每个顶点建立一条链表,记录从该顶点出发能够到达的邻接点。
邻接表的空间复杂度为:
O(n + e)
其中 n 是顶点数,e 是边数,因此更适合稀疏图。
3. 考试对比
|-----------|-------|----------|
| 比较项目 | 邻接矩阵 | 邻接表 |
| 空间 | O(n²) | O(n+e) |
| 判断两点是否有边 | O(1) | 需要扫描链表 |
| 遍历某顶点所有邻边 | O(n) | 与该顶点度数有关 |
| 适合 | 稠密图 | 稀疏图 |
高频判断:
稠密图优先邻接矩阵
稀疏图优先邻接表
四、图的遍历:DFS与BFS
1. 深度优先搜索 DFS
DFS的思路是沿着一条路径不断向深处走,直到走不下去,再回退到上一个顶点继续搜索。
它依赖:
递归或栈
大白话理解:
先一路走到底,再回头找其他路
2. 广度优先搜索 BFS
BFS先访问起点,再访问与起点距离为1的所有顶点,然后访问距离为2的所有顶点,逐层向外扩展。
它依赖:
队列
大白话理解:
先把附近一圈走完,再向外走下一圈
3. 复杂度
若使用邻接表:
DFS:O(n + e)
BFS:O(n + e)
若使用邻接矩阵:
DFS:O(n²)
BFS:O(n²)
4. 非连通图的处理
只从一个顶点开始DFS或BFS,只能访问该顶点所在的连通分量。
如果题目要求遍历整个非连通图,必须在外层再扫描所有顶点:
for (i = 0; i < n; i++) {
if (!visited[i]) {
DFS(i);
}
}
这是算法题和选择题中的高频遗漏点。
五、最小生成树:Prim与Kruskal
最小生成树解决的是:
在连通无向带权图中,选择n-1条边,
连接全部n个顶点,并使边权总和最小。
注意,它不是最短路径问题。
1. Prim算法
Prim从一个顶点出发,每次选择一条能够把"树内顶点"和"树外顶点"连接起来的最小权值边。
它关注的是:
顶点集合逐渐扩大
更适合:
稠密图
2. Kruskal算法
Kruskal先按边权从小到大排序,每次选择当前最小且不会形成回路的边,直到选出 n-1 条边。
它关注的是:
按边从小到大选择
通常配合并查集判断是否形成回路,更适合:
稀疏图
3. 快速区分
Prim:从一个点开始扩张
Kruskal:从最小边开始选择
4. 高频陷阱
最小生成树可能不唯一,但最小生成树的总权值一定相同。
若图不连通,则不存在生成树,只能得到生成森林。
六、最短路径:Dijkstra与Floyd
1. Dijkstra算法
Dijkstra用于求:
一个源点到其他所有顶点的最短路径
每一轮确定一个当前距离源点最近、且还未被确定的顶点,再用它更新其他顶点的距离。
限制:
不能处理负权边
如果题目中出现负权边,就不能直接使用Dijkstra。
2. Floyd算法
Floyd用于求:
任意两个顶点之间的最短路径
核心思想是逐个允许顶点作为中间点,并不断更新两点之间的最短距离。
更新公式:
D[i][j] = min(D[i][j], D[i][k] + D[k][j])
时间复杂度:
O(n³)
3. 快速区分
单源最短路:Dijkstra
任意两点最短路:Floyd
4. 不要和最小生成树混淆
最短路径关注的是:
某两个顶点之间怎么走最短
最小生成树关注的是:
怎样以最小总代价连接所有顶点
二者目标完全不同。
七、AOV网与拓扑排序
AOV网是:
用顶点表示活动的有向图
如果活动A必须在活动B之前完成,就画一条:
A -> B
AOV网必须是有向无环图,因为如果出现环,就会形成循环依赖。
1. 拓扑排序
拓扑排序的目标是得到一个满足所有先后关系的顶点序列。
标准做法:
1. 选择一个入度为0的顶点
2. 输出该顶点
3. 删除该顶点及其所有出边
4. 重复以上过程
如果最终没有输出全部顶点,说明图中存在有向环。
2. 拓扑序列是否唯一
不一定。
如果某一步同时存在多个入度为0的顶点,就可能产生多个合法拓扑序列。
八、AOE网与关键路径
AOE网是本题的核心。
AOE网中:
顶点表示事件
边表示活动
边权表示活动持续时间
这与AOV网正好相反:
AOV:顶点表示活动
AOE:边表示活动
1. 为什么关键路径是最长路径
AOE网通常表示一个工程。工程中不同活动可以并行进行,但整个工程必须等所有活动都完成以后才能结束。
因此,决定工程完成时间的是从源点到汇点所需时间最长的那条路径。
关键路径 = 从源点到汇点的最长路径
不是最短路径。
2. 关键活动
关键路径上的活动称为关键活动。关键活动没有时间余量,一旦延误,整个工程通常也会延误。
3. 事件最早发生时间 ve
从源点开始向后计算:
ve[j] = max(ve[i] + 边<i,j>的权值)
因为一个事件必须等所有前置活动都完成,所以取最大值。
4. 事件最迟发生时间 vl
从汇点开始反向计算:
vl[i] = min(vl[j] - 边<i,j>的权值)
为了保证整个工程不延期,事件最迟不能超过这个时间,所以取最小值。
5. 活动最早开始时间 e
若活动对应边 <i,j>,则:
e = ve[i]
6. 活动最迟开始时间 l
l = vl[j] - 边<i,j>的权值
7. 判断关键活动
时间余量 = l - e
若:
l - e = 0
则该活动是关键活动。
九、典型选择题:关于AOE网的说法
题目:
下列关于AOE网的说法正确的是:
A. AOE网是以顶点表示活动的网
B. 从源点到汇点的最短路径称为关键路径
C. 存在关键路径的AOE网一定是有向无环图
D. 缩短关键路径上某个活动的时间,整个工程时间必定缩短
正确答案是:
C
这道题可以整段判断。AOE网中顶点表示事件,边表示活动,因此A错误;关键路径决定整个工程的完成时间,所以它应当是源点到汇点的最长路径,而不是最短路径,因此B错误;AOE网表示工程活动之间的先后制约关系,若存在有向环,就会形成循环依赖,工程无法正常启动,因此AOE网必须是有向无环图,C正确;一个AOE网可能同时存在多条关键路径,如果只缩短其中一条关键路径上的某个活动,而另一条关键路径长度不变,那么总工期仍由另一条路径决定,因此D中的"必定缩短"错误。
十、这道题暴露出的思维漏洞
如果这道题完全忘记,说明不是单个知识点遗忘,而是下面几组概念没有建立对应关系:
AOV和AOE混淆
最短路径和关键路径混淆
拓扑排序和关键路径混淆
关键活动和普通活动混淆
需要重新建立下面这张表:
|------|--------------|
| 概念 | 核心含义 |
| AOV网 | 顶点表示活动 |
| AOE网 | 边表示活动,顶点表示事件 |
| 拓扑排序 | 给活动安排合法先后顺序 |
| 关键路径 | 源点到汇点的最长路径 |
| 关键活动 | 时间余量为0的活动 |
十一、408图这一章的高频陷阱
1. DFS一定用队列
错误。DFS依赖递归或栈,BFS才使用队列。
2. Dijkstra可以处理负权边
错误。Dijkstra不能处理负权边。
3. 最小生成树就是最短路径树
错误。一个是连接全部顶点的最小总成本,一个是从源点出发到各点最短。
4. 拓扑序列一定唯一
错误。若同时存在多个入度为0的顶点,拓扑序列可能不唯一。
5. AOE网中顶点表示活动
错误。AOE网中边表示活动,顶点表示事件。
6. 关键路径是最短路径
错误。关键路径是源点到汇点的最长路径。
7. 缩短一个关键活动一定缩短总工期
错误。存在多条关键路径时,只缩短其中一条未必改变总工期。
十二、考场快速选算法
看到题目后,可以直接按下面判断:
遍历整个图 -> DFS / BFS
无权图最短路径 -> BFS
单源带权最短路径且无负权 -> Dijkstra
任意两点最短路径 -> Floyd
连接全部顶点且总代价最小 -> Prim / Kruskal
判断任务先后顺序 -> 拓扑排序
求工程最短完成时间 -> 关键路径
十三、图这一章的复习顺序
建议按以下顺序复习:
1. 图的基本概念
2. 邻接矩阵与邻接表
3. DFS与BFS
4. 最小生成树
5. 最短路径
6. 拓扑排序
7. 关键路径
不要一开始就死记所有伪代码。先把每个算法"解决什么问题、使用什么数据结构、有什么限制"弄清楚,再补计算过程。
十四、一页速记
邻接矩阵:O(n²),适合稠密图
邻接表:O(n+e),适合稀疏图
DFS:递归/栈,一路走到底
BFS:队列,一层一层走
Prim:从点扩张
Kruskal:按边权从小到大选
Dijkstra:单源最短路,不能有负权边
Floyd:任意两点最短路,O(n³)
AOV:顶点表示活动,做拓扑排序
AOE:边表示活动,顶点表示事件
关键路径:源点到汇点的最长路径
关键活动:l-e=0
十五、回去重点复习哪里
数据结构
-> 图
-> 图的存储
-> 图的遍历
-> 最小生成树
-> 最短路径
-> 拓扑排序
-> 关键路径
当前最需要重点回看的部分是:
AOV网与AOE网的区别
拓扑排序的过程
ve、vl、e、l的含义
关键路径和关键活动的判断