图——拓扑排序
拓扑排序定义
给定一个包含 n 个节点的有向图 G,我们给出它的节点编号的一种排列,如果满足:
对于图 G 中的任意一条有向边 (u, v),u 在排列中都出现在 v 的前面。
那么称该排列是图 G 的「拓扑排序」。
题目与解析
lc 210. 课程表 II
我们可以将本题建模成一个求拓扑排序的问题了:
我们将每一门课看成一个节点;
如果想要学习课程 A之前必须完成课程 B,那么我们从 B 到 A 连接一条有向边。这样以来,在拓扑排序中,B 一定出现在A的前面。
思路:
用一个栈来存储所有已经搜索完成的节点。我们对图进行一遍深度优先搜索。当每个节点进行回溯的时候,我们把该节点放入栈中。最终从栈顶到栈底的序列就是一种拓扑排序。
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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | 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 出发通过一条有向边可以到达的所有节点。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)