题目描述
给定一个 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≤105
输入样例:
3 3
1 2
2 3
1 3
输出样例:
1 2 3
前置知识
题目分析
拓扑序列的特征:
由一个图中的各点构成一个线性序列,若图中存在边<a, b>,则构成的序列中a一定在b的前面。
在构建拓扑序列过程中,每次将图中的结点添加到序列中时需要该结点的入度为0才可被添加进来。由此可知,存在环的图一定不能构成拓扑序列。而有向无环图一定可以构成拓扑序列。
因此,根据此规则,可选用BFS算法,来保证拓扑序列中结点间的先后次序进行实现。每次将入度为0的加入到队列中,并将该店所指向的下一个结点的入度减一。这一过程需要使用一个数组d[N]进行记录。
当所有的序列均可以按照规则放入到队列时,则该图可进行拓扑排序构成拓扑序列,反之则不能。
算法实现
#include <stdio.h>
int n, m; // 点数n和边数m
const int N = 1e5 + 10;
int h[N], e[N], ne[N], idx; // 构建邻接矩阵
int q[N], d[N]; // 队列q和记录入度数组d
void add(int a, int b){ // 头插法
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
bool topsort(){
int front = 0, rear = 0;
for(int i = 1; i <= n; i++) // 从编号1开始将所有入度为0的点入队
if(!d[i]) q[rear++] = i;
while(front < rear){
int x = q[front++];
for(int i = h[x]; i != -1; i = ne[i]){ // 获取入度为0的点的下一个邻接点
int j = e[i];
d[j]--; // “删除”结点x后,与之相邻的结点j入度减一
if(!d[j]) q[rear++] = j; // 当结点j入度为0时,将其入队
}
}
return rear == n; // 当所有序列均入队时,说明是按照拓扑排序入队,反之则不为拓扑序列
}
int main(){
scanf("%d%d", &n, &m);
for(int i = 0; i <= n; i++) h[i] = -1; // 初始化邻接矩阵中的顶点表指向“空指针”
while(m--){
int a, b; scanf("%d%d", &a, &b);
add(a, b);
d[b]++; // 结点b的入度加一
}
if(topsort()){ // 若为拓扑排序,则输出拓扑序列
for(int i = 0; i < n; i++) printf("%d ", q[i]);
puts("");
}else
puts("-1");
return 0;
}