拓扑排序和拓扑序
众所周知,拓扑序可以决定 dp 的顺序,而拓扑排序则是用于求图上的点的拓扑序。
对于图上任意两点 \(x,y(x\ne y)\),当 \(x\) 的拓扑序大于 \(y\) 的拓扑序时,\(x\) 不能到达 \(y\)。所以在有向有环图和无向图上是不存在拓扑序的。
而拓扑排序则是用于求 DAG(有向无环图)的拓扑序。
思想
从小到大分配拓扑序。
对于一个节点 \(x\),如果它的入度为 \(0\),那么就代表在原图上所有可以到达 \(x\) 的点的拓扑序都小于 \(x\) 的拓扑序,那么就删除节点 \(x\),继续进行下一次分配。
实现
用一个队列来维护入度为 \(0\) 的节点。
首先把所有原图中的入度为 \(0\) 压入队列,然后每次取出队头元素 \(x\),删除它。
删除就是将所有 \(x\) 连向的节点的入度减 \(1\),如果其中有一个节点的入度变为 \(0\) 了,将其压入队列即可。
B3644 【模板】拓扑排序 / 家谱树
虽然这题 \(n\) 范围很小,但是就把它当 \(10^5\) 这种级别来做。
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
int n, x, b[110];
vector<int> g[110];
queue<int> q;
int main () {
ios::sync_with_stdio(0), cin.tie(0);
cin >> n;
for (int i = 1; i <= n; i++) {
while (cin >> x && x) {
g[i].push_back(x), b[x]++;
}
}
for (int i = 1; i <= n; i++) {
if (!b[i]) {
q.push(i);
}
}
for (int i = 1; i <= n; i++) {
x = q.front();
q.pop();
cout << x << ' ';
for (int j : g[x]) {
b[j]--;
if (!b[j]) {
q.push(j);
}
}
}
return 0;
}
令 \(n\) 为节点数,\(m\) 为边数,所有复杂度为 \(O(n+m)\)。
一些应用
- 拓扑序决定了 dp 的顺序,因此当要做图上 dp 时可以直接边拓扑排序边 dp。
- 给定一个有向图,可以通过拓扑排序来判环,当拓扑排序后仍有节点没有被规定拓扑序时就是有环。