拓扑排序
有向无环图
英文名: Directed Acyclic Graph 简称 DAG
性质
- 能 拓扑排序 的图一定是 有向无环图
如果有环, 那么环上任意两个节点在任意序列都不满足条件了 - 有向无环图 一定能 拓扑排序
判定
检验它是否可以进行 拓扑排序 即可。
当然也有另外的方法,可以对图进行一遍 DFS, 在得到的 DFS 树上看有没有连向祖先的非树边(返祖边)。如果有的话,那就有环了。
拓扑排序
拓扑排序的英文名是 Topological sorting。
拓扑排序要解决的问题是 给一个图的所有节点排序。
无法对一个有环的图进行排序
因此我们可以说 在一个 DAG 中,我们将图中的顶点以线性方式进行排序,使得对于任何的顶点 u 到 v 的有向边 , 都可以有 u 在 v 的前面。
还有给定一个 DAG,如果从 i 到 j 有边,则认为 j 依赖于i 。如果 i 到 j 有路径( 可达 ),则称 j 间接依赖于 i。
拓扑排序的目标是将所有节点排序,使得排在前面的节点不能依赖于排在后面的节点。
Kahn 算法
初始状态下, 集合 C 装着所有入度为 0 的点, list 是一个空表
每次从S 随意取出一个点 u , 放入list, 然后把所有 u 的边 (u, v1), (u, v2) … 删除
对于边 (u, v), 若删除该边后, v 的入度为0, 则把 v 放入list 中
不断重复, 直到 S 为空. 检查图中是否存在任何边, 如果存在, 则一定有环, 否则返回L, L 的顺序则为拓扑排序的结果
核心: 维持一个入度为 0 的顶点的集合。
时间复杂度: O(E + V)
类似 bfs
伪代码:
bool toposort() {
q = new queue();
for (i = 0; i < n; i++)
if (in_deg[i] == 0) q.push(i);
ans = new vector();
while (!q.empty()) {
u = q.pop();
ans.push_back(u);
for each edge(u, v) { // 遍历所有(u, v) 如果v入度为1 则放入S
if (--in_deg[v] == 0) q.push(v);
}
}
if (ans.size() == n) {
for (i = 0; i < n; i++)
std::cout << ans[i] << std::endl;
return true;
} else {
return false;
}
}
求字典序最大/ 最小的拓扑排序:
将 Kahn 算法中的队列替换成最大堆/最小堆实现的优先队列即可
dfs:
vector<int> G[MAXN]; // vector 实现的邻接表
int c[MAXN]; // 标志数组
vector<int> topo; // 拓扑排序后的节点
bool dfs(int u) {
c[u] = -1;
for (int v : G[u]) {
if (c[v] < 0)
return false;
else if (!c[v])
if (!dfs(v)) return false;
}
c[u] = 1;
topo.push_back(u);
return true;
}
bool toposort() {
topo.clear();
memset(c, 0, sizeof(c));
for (int u = 0; u < n; u++)
if (!c[u])
if (!dfs(u)) return false;
reverse(topo.begin(), topo.end());
return true;
}
应用:
拓扑排序可以用来判断图中是否有环,
还可以用来判断图是否是一条链。