拓扑排序
# 拓扑排序
# lc207. 课程表中等
题目描述
你这个学期必须选修 numCourses
门课程,记为 0
到 numCourses - 1
。
在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites
给出,其中 prerequisites[i] = [ai, bi]
,表示如果要学习课程 ai
则 必须 先学习课程 bi
。
- 例如,先修课程对
[0, 1]
表示:想要学习课程0
,你需要先完成课程1
。
请你判断是否可能完成所有课程的学习?如果可以,返回 true
;否则,返回 false
。
示例 1:
输入:numCourses = 2, prerequisites = [[1,0]]
输出:true
解释:总共有 2 门课程。学习课程 1 之前,你需要完成课程 0 。这是可能的。
1
2
3
2
3
示例 2:
输入:numCourses = 2, prerequisites = [[1,0],[0,1]]
输出:false
解释:总共有 2 门课程。学习课程 1 之前,你需要先完成课程 0 ;并且学习课程 0 之前,你还应先完成课程 1 。这是不可能的。
1
2
3
2
3
思路
构建邻接表 graph
和入度数组 inDegree
初始化队列,把所有入度为 0
的节点加入队列
BFS 出队:每出队一个节点 u
,就减少它指向节点 v
的入度
如果某个 v
入度变为 0
,加入队列
最终出队节点数量等于课程数 → ✅ 没有环
function canFinish(numCourses, prerequisites) {
const graph = new Map(); // 邻接表
const inDegree = new Array(numCourses).fill(0); // 入度数组
// 1. 初始化图结构和入度
for (const [to, from] of prerequisites) {
if (!graph.has(from)) graph.set(from, []);
graph.get(from).push(to);
inDegree[to]++;
}
// 2. 初始化队列,加入所有入度为 0 的课程
const queue = [];
for (let i = 0; i < numCourses; i++) {
if (inDegree[i] === 0) queue.push(i);
}
// 3. 拓扑排序(BFS)
let visitedCount = 0;
while (queue.length > 0) {
const course = queue.shift();
visitedCount++;
const neighbors = graph.get(course) || [];
for (const nextCourse of neighbors) {
inDegree[nextCourse]--;
if (inDegree[nextCourse] === 0) {
queue.push(nextCourse);
}
}
}
// 4. 是否访问完所有课程?
return visitedCount === numCourses;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
时间复杂度 O(n + m)
步骤 操作 复杂度 初始化入度数组 遍历 prerequisites
,统计每个节点的入度O(m)
建图(邻接表) 把每个依赖关系转成图 O(m)
遍历课程 找出所有入度为 0 的节点,入队 O(n)
BFS 遍历图 最多遍历每个节点和每条边 O(n + m)
空间复杂度 O(n + m)
数据结构 | 内容 | 空间复杂度 |
---|---|---|
inDegree 数组 | 长度为 n 的入度数组 | O(n) |
graph 邻接表 | 每个节点对应的邻居列表,存 m 条边 | O(m) |
queue 队列 | 最坏情况,所有节点都进队 | O(n) |
# lc210. 课程表 II中等
题目描述
现在你总共有 numCourses
门课需要选,记为 0
到 numCourses - 1
。给你一个数组 prerequisites
,其中 prerequisites[i] = [ai, bi]
,表示在选修课程 ai
前 必须 先选修 bi
。
- 例如,想要学习课程
0
,你需要先完成课程1
,我们用一个匹配来表示:[0,1]
。
返回你为了学完所有课程所安排的学习顺序。可能会有多个正确的顺序,你只要返回 任意一种 就可以了。如果不可能完成所有课程,返回 一个空数组 。
示例 1:
输入:numCourses = 2, prerequisites = [[1,0]]
输出:[0,1]
解释:总共有 2 门课程。要学习课程 1,你需要先完成课程 0。因此,正确的课程顺序为 [0,1] 。
1
2
3
2
3
示例 2:
输入:numCourses = 4, prerequisites = [[1,0],[2,0],[3,1],[3,2]]
输出:[0,2,1,3]
解释:总共有 4 门课程。要学习课程 3,你应该先完成课程 1 和课程 2。并且课程 1 和课程 2 都应该排在课程 0 之后。
因此,一个正确的课程顺序是 [0,1,2,3] 。另一个正确的排序是 [0,2,1,3] 。
1
2
3
4
2
3
4
示例 3:
输入:numCourses = 1, prerequisites = []
输出:[0]
1
2
2
思路
就是要把上一题的输出出来
function findOrder(numCourses, prerequisites) {
const graph = new Map();
const inDegree = new Array(numCourses).fill(0);
// 建图 + 统计入度
for (const [to, from] of prerequisites) {
if (!graph.has(from)) graph.set(from, []);
graph.get(from).push(to);
inDegree[to]++;
}
const queue = [];
const res = [];
// 将入度为 0 的课程加入队列
for (let i = 0; i < numCourses; i++) {
if (inDegree[i] === 0) queue.push(i);
}
// 拓扑排序
while (queue.length > 0) {
const course = queue.shift();
res.push(course);
const neighbors = graph.get(course) || [];
for (const nextCourse of neighbors) {
inDegree[nextCourse]--;
if (inDegree[nextCourse] === 0) {
queue.push(nextCourse);
}
}
}
// 是否所有课程都加入了结果中
return res.length === numCourses ? res : [];
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
上次更新: 2025/05/27, 15:44:05