题目链接 : 点击查看
题目描述 :
给定一个 n 个点 m 条边的有向图,点的编号是 1 到 n,图中可能存在重边和自环。请输出任意一个该有向图的拓扑序列,如果拓扑序列不存在,则输出 −1。若一个由图中所有点构成的序列 A 满足:对于图中的每条边 (x,y),x 在 A 中都出现在 y 之前,则称 A 是该图的一个拓扑序列。
输入输出格式:
输入
第一行包含两个整数 n 和 m。
接下来 m 行,每行包含两个整数 x 和 y,表示存在一条从点 x 到点 y 的有向边 (x,y)。
输出
共一行,如果存在拓扑序列,则输出任意一个合法的拓扑序列即可。
否则输出 −1。
输入输出样例 :
输入
3 3
1 2
2 3
1 3
输出
1 2 3
题目分析:
拓扑排序的定义为 : 将有向图中的顶点以线性方式进行排序。即对于任何连接自顶点u到顶点v的有向边<u,v>,在最后的排序结果中,顶点u总是在顶点v的前面。拓扑排序的常见用途就是判断有向图是否有环,相应地判断无向图是否有环常用并查集。拓扑排序算法可以分成两步走 : 1. 将图中所有入度为 0 的点入队 2. 不断将队头元素出队,将其发出弧的终点的入度相应减一,接着搜索到下一个点,在此过程中入度减为0的点持续入队,重复上述过程直到队列空。出队时用数组进行记录,最后将数组中元素依次输出即为此图的拓扑序列。
具体做法为: 我们用数组q[N]来模拟队列,用d[N]来记录每个点的入度。记录入度的做法为,在对图进行储存时,插入<x, y> 我们令d[y] ++即可。关于图的存储,我们一般采用邻接表的方式,这部分详见代码,不过多展示。然后我们创建一个bool类型的topsort函数,最后的返回值为tt == n - 1(tt是队尾指针,队列数组的下标从0开始),如果相等,则图有拓扑序列,反之则此图有环无法进行拓扑排序。而且在此我们用数组模拟队列的好处是,不用再另开一个数组来储存入队结果,其出队结果只是将队头指针hh++,原本值仍在。然后开始我们的核心操作,首先用一个for循环将入度为0的点( i 从 1 到 n 代表节点编号)入队,q[ ++ tt] = i。接着开始bfs,以队列为空为while循环结束标准,每次循环将队头出队,然后遍历邻接表去搜索当前队头的下一个节点,因为队头出队相当于删除操作图中节点操作,所以要将其下一个节点的入度数减一,即 d [j] --, 并进行判断d[j]是否为0的判断,如为0,将当前节点入队。重复以上操作,直至队列为空。详见如下代码。
代码 :
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 1e5 + 7;
int n, m;
int h[N], e[N], ne[N], cur;
int d[N];//d数组储存每个点的入度
int q[N];//模拟队列
void add(int a, int b) {//用邻接表对图进行储存
e[cur] = b;
ne[cur] = h[a];
h[a] =cur ++ ;
}
bool topsort() {
int hh = 0, tt = -1;
for (int i = 1; i <= n; i ++ ) {
if (!d[i]) {
q[ ++ tt] = i;//将所有入度为0的点先入队
}
}
while (hh <= tt) {
int t = q[hh ++ ];//将对头元素出队
for (int i = h[t]; i != -1; i = ne[i]) {
int j = e[i];
if (-- d[j] == 0) {//把已经搜索过的点删去,所以其下一个点入度减一
q[ ++ tt] = j;//入队
}
}
}
return tt == n - 1;//是否队尾到图的末尾,若到达,则全部元素间都可以进行拓扑排序,否则图中无拓扑序列
}
int main() {
cin >> n >> m;
memset(h, -1, sizeof(h));
for (int i = 0; i < m; i ++ ) {
int a, b;
cin >> a >> b;
add(a, b);
d[b] ++ ;//add(a, b)表明有a指向b的一条有向边 b的入度要加1
}
if (!topsort()) puts("-1");
else {
for (int i = 0; i < n; i ++ ) printf("%d", q[i]);
cout << endl;
}
return 0;
}
下面给出拓扑排序的相关模板
时间复杂度 O(n+m), n 表示点数,m 表示边数
bool topsort()
{
int hh = 0, tt = -1;
// d[i] 存储点i的入度
for (int i = 1; i <= n; i ++ )
if (!d[i])
q[ ++ tt] = i;
while (hh <= tt)
{
int t = q[hh ++ ];
for (int i = h[t]; i != -1; i = ne[i])
{
int j = e[i];
if (-- d[j] == 0)
q[ ++ tt] = j;
}
}
// 如果所有点都入队了,说明存在拓扑序列;否则不存在拓扑序列。
return tt == n - 1;
}