
会编程的土豆 • 计科学习者
🔥 个人主页: 点击访问我的主页
📘 核心专栏1: 数据结构与算法
📘 核心专栏2: LeetCode Hot 100
✨ 面包会有的,牛奶会有的,一切都会有的!
拓扑排序:一篇就够了!从入门到精通
你还在为课程安排、任务调度发愁吗?拓扑排序就是你的救星!
什么是拓扑排序?
想象一下,你要安排一学期的课程,有些课必须先修其他课才能上:
- 微积分 → 线性代数 → 机器学习
拓扑排序就是:对DAG(有向无环图)的所有顶点进行线性排序,使得对于每条边 u→v,u 都出现在 v 之前。
核心关键词
- DAG:有向无环图(Directed Acyclic Graph)
- 线性排序:排成一列
- 依赖关系:谁必须在谁前面
为什么需要拓扑排序?
| 应用场景 | 具体问题 | 拓扑排序的作用 |
|---|---|---|
| 🎓 课程安排 | 先修课依赖 | 排出合理的学习顺序 |
| 🔧 编译构建 | 文件依赖 | 确定编译顺序 |
| 📦 包管理 | 依赖关系 | 解决安装顺序 |
| 🏗️ 项目管理 | 任务依赖 | 制定项目计划 |
🚀 核心算法一:Kahn算法(入度法)
核心思想
不断删除入度为0的节点,就像剥洋葱一样层层剥开!
算法步骤
python
1. 计算每个节点的入度
2. 将入度为0的节点加入队列
3. 循环处理:
- 取出队首节点,加入结果
- 将它指向的节点入度减1
- 如果入度变为0,加入队列
4. 判断结果数量 == 节点总数
c++
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
vector<int> topologicalSortKahn(vector<vector<int>>& graph, int n) {
// 1. 计算入度
vector<int> inDegree(n, 0);
for (int u = 0; u < n; u++) {
for (int v : graph[u]) {
inDegree[v]++;
}
}
// 2. 入度为0的节点入队
queue<int> q;
for (int i = 0; i < n; i++) {
if (inDegree[i] == 0) {
q.push(i);
}
}
// 3. 核心处理
vector<int> result;
while (!q.empty()) {
int u = q.front();
q.pop();
result.push_back(u);
for (int v : graph[u]) {
inDegree[v]--;
if (inDegree[v] == 0) {
q.push(v);
}
}
}
// 4. 检查是否有环
if (result.size() != n) {
return {}; // 存在环
}
return result;
}
执行过程图解
java
初始图:
0 ──→ 1 ──→ 3
│
└──→ 2 ──→ 3
步骤1: 入度[0]=0 → 队列[0]
步骤2: 处理0,解锁1和2 → 队列[1,2]
步骤3: 处理1,解锁3(入度3→1) → 队列[2]
步骤4: 处理2,解锁3(入度3→0) → 队列[3]
步骤5: 处理3 → 完成
结果: [0, 1, 2, 3] ✓
核心算法二:DFS算法(深度优先)
核心思想
沿着依赖链走到尽头,回溯时记录节点
算法步骤
java
1. 对每个未访问节点执行DFS
2. DFS过程中:
- 标记为"访问中"
- 递归访问所有邻居
- 发现"访问中"节点 → 存在环
- 回溯时压入栈
3. 栈反转得到拓扑序
完整代码实现
java
class TopologicalSortDFS {
private:
vector<vector<int>> graph;
vector<int> visited; // 0=未访问, 1=访问中, 2=已完成
stack<int> stk;
bool hasCycle = false;
void dfs(int u) {
visited[u] = 1; // 标记访问中
for (int v : graph[u]) {
if (visited[v] == 1) { // 发现环!
hasCycle = true;
return;
}
if (visited[v] == 0) {
dfs(v);
if (hasCycle) return;
}
}
visited[u] = 2; // 标记完成
stk.push(u); // 回溯时记录
}
public:
vector<int> sort(vector<vector<int>>& g, int n) {
graph = g;
visited.assign(n, 0);
for (int i = 0; i < n; i++) {
if (visited[i] == 0) {
dfs(i);
if (hasCycle) return {};
}
}
vector<int> result;
while (!stk.empty()) {
result.push_back(stk.top());
stk.pop();
}
return result;
}
};