图——拓扑排序
拓扑排序定义
给定一个包含 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 出发通过一条有向边可以到达的所有节点。