AcWing 848. 有向图的拓扑序列(基于bfs的topsort)

题目链接点击查看

题目描述

给定一个 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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

在森林中麋了鹿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值