拓扑排序定义
给定一个包含 n 个节点的有向图 G,我们给出它的节点编号的一种排列,如果满足:
对于图 G 中的任意一条有向边 (u, v),u 在排列中都出现在 v 的前面。
那么称该排列是图 G 的「拓扑排序」。
题目与解析
lc 210. 课程表 II
我们可以将本题建模成一个求拓扑排序的问题了:
我们将每一门课看成一个节点;
如果想要学习课程 A之前必须完成课程 B,那么我们从 B 到 A 连接一条有向边。这样以来,在拓扑排序中,B 一定出现在A的前面。
思路:
用一个栈来存储所有已经搜索完成的节点。我们对图进行一遍深度优先搜索。当每个节点进行回溯的时候,我们把该节点放入栈中。最终从栈顶到栈底的序列就是一种拓扑排序。
class Solution {
private:
// 存储有向图
vector<vector<int>> edges;
// 标记每个节点的状态:0=未搜索,1=搜索中,2=已完成
vector<int> visited;
// 用数组来模拟栈,下标 0 为栈底,n-1 为栈顶
vector<int> result;
// 判断有向图中是否有环
bool valid = true;
public:
void dfs(int u) {
// 将节点标记为「搜索中」
visited[u] = 1;
// 搜索其相邻节点
// 只要发现有环,立刻停止搜索
for (auto v: edges[u]) {
// 如果「未搜索」那么搜索相邻节点
if (visited[v] == 0) {
dfs(v);
}
// 如果「搜索中」说明找到了环
if (visited[v] == 1) {
valid = false;
return;
}
}
// 将节点标记为「已完成」
visited[u] = 2;
// 将节点入栈
result.push_back(u);
}
vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) {
edges.resize(numCourses); // 从每个节点出发的有向边
visited.resize(numCourses);
// 构建有向图
for (const auto& info: prerequisites) { //注意这里const auto& info的含义, 用和不用都能通过
edges[info[1]].push_back(info[0]); //从info[1]的节点指向info[0]的节点(可以有中间节点)
}
// 每次挑选一个「未搜索」的节点,开始进行深度优先搜索
for (int i = 0; i < numCourses; i++) {
if (!visited[i]) {
dfs(i);
}
}
if (!valid) { // 有环
return {};
}
// 如果没有环,那么就有拓扑排序
// 注意下标 0 为栈底,因此需要将数组反序输出
reverse(result.begin(), result.end());
return result;
}
};
参考链接:
https://leetcode.cn/problems/course-schedule-ii/solution/ke-cheng-biao-ii-by-leetcode-solution/
*题解中「相邻节点」指的是从 u 出发通过一条有向边可以到达的所有节点。