拓扑排序
概述
对一个DAG(有向无环图)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,如果存在一条有向边u−>v则u在v之前出现。简单来说,是在不破坏节点先后顺序的情况下,把DAG拉成一条链。
注意:拓扑排序可能不是唯一的。
Kahn算法
首先获取所有**入度为0**的节点,将他们排在前面然后从图中删除。此时有些点的入度会减少,重复前面的操作直到所有点都被删除(输出)。
我们可以通过BFS来实现,使用一个队列,首先遍历入度为0的节点(这些节点入队),队首出队,与队首所连节点度数−1,如果有节点度数减为0将其加入队列,重复上述操作直至队列为空。
示例代码
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
| struct edge { int to; int NextE; } e[100]; int num_e = 0; int head[100]; int num_D[100]; vector<int> ToPu(int n) { vector<int> a; int cnt = 0; queue<int> q; for (int i = 1; i <= n; i++) { if (num_D[i] == 0) { q.push(i); } } while (!q.empty()) { int x = q.front(); q.pop(); a.push_back(x); for (int i = head[x]; i; i = e[x].NextE) { num_D[e[i].to]--; if (num_D[e[i].to] == 0) { q.push(e[i].to); } } } return a; }
|
基于DFS
我们可以在DFS的过程中使用一个栈来记录访问的顺序,由定义可知位于最"深"层的节点经过拓扑排序后会在最后输出,而DFS是直到最"深"层的节点才开始递归。所以我们可以在将该节点先存入栈中,最后再输出。
即当某节点的所有子节点都入栈后,才将该节点入栈。
示例代码
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
| void DFS(int i, vector<bool> &vis, stack<int> &a) { vis[i] = true;
for (int j = head[i]; j; j = e[i].NextE) { if (!vis[e[j].to]) { DFS(e[j].to, vis, a); } }
a.push(i); } stack<int> ToPuDFS(int n) { stack<int> a; vector<bool> vis(n + 1, false);
for (int i = 1; i <= n; i++) { if (!vis[i]) DFS(i, vis, a); }
while (!a.empty()) { cout << a.top() << " "; a.pop(); } }
|
应用
判断有向图是否无环
注意到拓扑排序只能对有向无环图进行,所以反之,如果一个有向图无法进行拓扑排序那他一定有环。
- 在kahn算法下我们只需要判断最后是否有n个输出就能确定是否有环(即是否所有点都被输出)。
- 在DFS中我们只需要再增加一个数组用来记录当前已经经过但还未入栈的节点,如果我们在向"深处"的时候又遇见它们则说明该图存在环。
求有向无环图中的最短路径
详见图中的最短路径问题