拓扑排序
💡 核心思想
拓扑排序将 DAG(有向无环图)中的所有顶点排成一个线性序列,使得每条边的起点都在终点之前。常用 Kahn 算法(BFS,不断删除入度为0的节点)实现。也可用于检测图中是否有环。
拓扑排序的核心就是"先做没有前置依赖的事"。入度为0的节点就是当前可以做的事,做完后把它指向的节点的入度减1,如果也变成0就可以做了。如果最后还有节点没处理完,说明图里有环。
🎯 直觉理解
想象你要安排课程表:有些课有先修要求(数据结构需要先学编程)。拓扑排序就是找到一种上课顺序,确保每门课的先修课都排在它前面。如果课程之间有循环依赖(A要B先修,B要A先修),就不可能排出顺序——说明有环。
📝 算法流程
- 计算所有节点的入度
- 将入度为0的节点入队
- 出队一个节点,将其所有邻居入度减1
- 若邻居入度变为0则入队,重复直到队空
$$\text{如果拓扑序列长度} < n \text{,则图有环}$$
📊 复杂度分析
| 指标 | 复杂度 |
|---|---|
| 时间 | $O(V+E)$ |
| 空间 | $O(V+E)$ |
💻 参考实现(C++)
C++ (C++17)
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, m; cin >> n >> m;
vector<vector<int>> adj(n+1);
vector<int> indeg(n+1, 0);
for (int i = 0; i < m; i++) {
int u, v; cin >> u >> v;
adj[u].push_back(v);
indeg[v]++;
}
queue<int> q;
for (int i = 1; i <= n; i++)
if (indeg[i] == 0) q.push(i);
vector<int> order;
while (!q.empty()) {
int u = q.front(); q.pop();
order.push_back(u);
for (int v : adj[u]) {
if (--indeg[v] == 0) q.push(v);
}
}
if (order.size() < n) cout << "有环" << endl;
else { for (int x : order) cout << x << " "; cout << endl; }
return 0;
}⚠️ 常见坑点
忘记检测环(序列长度 < n)
入度数组初始化为0后忘记统计
DAG上的DP需要先拓扑排序确定计算顺序
队列中多个入度为0的节点的处理顺序影响字典序
📚 相关题目
| 题目 | 来源 | 难度 | 备注 |
|---|---|---|---|
| P1113 杂务 | 洛谷 | CSP-S | 拓扑排序+DP |
| P4017 最大食物链计数 | 洛谷 | CSP-S | 拓扑排序+计数DP |