题目描述
- 给定一个
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 ≤ 10^5
基本思路
- 本题仍然属于树的广度优先遍历的模板题,下面的代码中给出了非常详细的注释,可以直接参阅代码内容即可。如果还是不太明白,可以试一试按照代码的执行顺序看一遍。
实现代码
#include <cstdio>
#include <vector>
using namespace std;
// 【输入获得】图中点的数量和边的数量
int n, m;
// 【常量定义】图中的点的数量和边的数量的上限
const int N = 100010;
// 【中间变量】存储有向边的邻接表、存储每个结点的入度的数组、存储已经被删除的元素
vector<int> adjacent_table[N];
int in_degree[N];
bool is_deleted[N];
// 【自定义函数】基于广度优先搜索算法(BFS)获取一个有向图中的拓扑序列并进行输出的函数,如果拓扑序列不存在则输出-1
void getTopologicalOrdering(void)
{
// 【变量定义】记录已经被放入过拓扑序列中的点、某轮遍历过程中入度为0的点的个数
vector<int> visited;
int zero_inDegree;
// 【算法核心】通过do-while循环,每次找到当前所有入度为0的点,将这些点放入拓扑序列中并删除这些点的出边
// 当某一轮遍历中不存在入度为0的点则结束循环
do
{
// 【变量初始化】将入度为0的点的个数重新修改为0
zero_inDegree = 0;
// 查找数组,找到所有未被删除且入度为0的点,需要注意点的编号从1开始
for(int i = 1; i <= n; ++ i)
{
if(is_deleted[i] == false && in_degree[i] == 0)
{
// 修改当前一轮遍历中入度为0的点的个数
++ zero_inDegree;
// 将当前点放入拓扑排序中
visited.push_back(i);
// 标记该点删除
is_deleted[i] = true;
// 删除该点的所有出边,只需要将该边的终点的入度减一即可
for(int edge : adjacent_table[i]) -- in_degree[edge];
}
}
}while(zero_inDegree != 0);
// 【判定】情况1:如果最终没有完成对图中所有点的遍历,则说明不存在一个拓扑排序,返回-1
if(visited.size() != n) printf("-1");
// 情况2:如果最终完成了对图中所有点的遍历,则说明存在一个拓扑排序,则将向量中的点按照顺序输出即可
else for(int point : visited) printf("%d ", point);
}
int main(void)
{
// 【输入】图中点的数量和边的数量
scanf("%d%d", &n, &m);
// 【输入和信息存储】每一次输入一条有向边
for(int i = 0; i < m; ++ i)
{
// 输入一条有向边
int a, b;
scanf("%d%d", &a, &b);
// 使用邻接表存储有向边
adjacent_table[a].push_back(b);
// 将有向边终点的入度加一
++ in_degree[b];
}
// 【获取结果】使用自定义的函数获得最终的一个拓扑序列
getTopologicalOrdering();
return 0;
}