题目
给定一个 n 个点 m 条边的有向图,点的编号是 1 到 n,图中可能存在重边和自环。
请输出任意一个该有向图的拓扑序列,如果拓扑序列不存在,则输出 −1。
若一个由图中所有点构成的序列 A 满足:对于图中的每条边 (x,y),x 在 A 中都出现在 y 之前,则称 A 是该图的一个拓扑序列。
输入格式
第一行包含两个整数 n 和 m。
接下来 m 行,每行包含两个整数 x 和 y,表示存在一条从点 x 到点 y 的有向边 (x,y)。
输出格式
共一行,如果存在拓扑序列,则输出任意一个合法的拓扑序列即可。
否则输出 −1。
数据范围
1≤n,m≤10e5
输入样例:
3 3
1 2
2 3
1 3
输出样例:
1 2 3
笔记
问题分析
把每个变量看成一个点, 点与点关系看做有向边(小于), 于是得到一个有向图。 在一个有向图中把所有节点排序, 使得每一条(u,v)对应的 u 都排在 v 的前面。 这个问题在图论中被称为 拓扑排序(topological sort)
很显然, 如果该图有环, 即一个点u 既在点 v 的前面, 又在点 v 的后面, 此排序在逻辑上是不通的。
不包含有向环的有向图成为有向无环图DAG(Directed Acyclic Graph)
求解策略
当所有 大于 v 的点 处理过后, 便可以处理 v点。 即一个点的 入度 为 0。
那么刚开始时, 遍历整图, 所有初始入度为0的点加入队列中。 假设加入初始入度为0的 点u, 该点有连通v的边, 那么就将v的入度减去1, 以便下一轮处理。
处理操作:
遍历图:
如果该点入度为0:
加入到队列中
遍历u的子节点:
v的入度 -= 1
结果队列末尾 = u
现在处理好头节点后, 如何处理后续节点呢? 求解过程以层级来分, 第一层我们把初始入度为0的点加入队列中。 接着要处理第二层, 而第二层的点数量由第一层所决定, 第一层剥开后产生的新的入度为0的点 是第二层的点。 接着把第二层的点进行同样的处理得出第三层的点。 直到下一层的点数为0.
这每一层的新加的点如何存储以便于下一轮的处理呢? 可以用先入先出的队列来存储, 第一层中每个点的子节点若符合要求(入度-1后为0)则加入到队列里, 之后处理队列中的, 直到队列为空则结束。
即
遍历图:
如果该点入度为0:
加入到队列中
若队列不为空:
u = 队列头节点
遍历u的子节点:
v的入度 -= 1
结果数列末尾 = u
核心代码为:
for(int i = 1; i <= n;i ++)
if(!indegree[i])
q.push(i);
while(!q.empty())
{
int u = q.front();
q.pop();
// 链式图, 邻接矩阵写法参考后文
for(int i = h[u]; i != -1; i = ne[i])
{
int j = e[i];
if(--indegree[j] == 0)
q.push(j);
}
ans[++idx] = j;
}
若使用数组模拟队列, 则可以省去 ans 数组
for(int i = 1; i <= n;i ++)
if(!indegree[i])
q[++tt] = i;
while(hh <= tt)
{
int u = q[hh++];
// 链式图, 邻接矩阵与链式图的转换参考我另一篇文章
for(int i = h[u]; i != -1; i = ne[i])
{
int j = e[i];
if(--indegree[j] == 0)
q[++tt] = j;
}
}
for(int i = 0; i < n; i++) // q数组本身便是ans数组
cout << q[i] << " ";
如何判断该图是否为有向无环图呢? 看看ans数组长度是否为n就行
以上求法可以归为 BFS 求法, 还有 DFS 求法, 紫书上便是如此。
如果说BFS是求的入度为0的点, 那么可以说 DFS 求的是出度为0的点。 从图上任意一点开始, 深度搜索子节点, 直到找到出度为0的点, 将该点存入 ans队列的首部(从位置n开始插, 插完将指针左移, 即逆序插入)。 接着将上一个节点继续加入ans, 上上一个节点加入ans, 直到起始节点。
bool dfs(int u){
c[u]=-1;
for(int v=h[u]; v != -1; v = ne[v]) {
int j = e[v];
if(c[j]<0) return false;
else if(!c[j] && !dfs(j)) return false;
}
c[u]=1;
ans[--t]=u;
return true;
}
bool toposort(){
t=n;
for(int i=0;i<n;i++){ if(!c[i])
if(!dfs(i)) return false;
}
return true;
}