有向图的拓扑序列(拓扑排序)

给定一个 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

输入样例:
3 3
1 2
2 3
1 3
输出样例:
1 2 3

有向无环图可以得到拓扑排序,拓扑排序就是只有从前指向后的边,比如图里面有一条从a指向b的边,那么在拓扑序列里面,a一定在b的前面。可以参考这篇文章:拓扑排序 (算法思想+图解+模板+练习题)_拓扑排序模板-CSDN博客

我们求一个拓扑序列,可以使用队列的方式,先找到图里面入度为0的点作为序列的起点,再将这些点删除,然后找到他们指向的点,此时这些指向的点存在入度为0的情况,再次入队,以此类推,知道图里所有点都入队为止。

bool topsort()
{
    int hh=0,tt=-1;  //队头队尾
    
    for(int i=1;i<=n;i++) //点的编号是从1开始的,从头开始找入度为0的点
    {
        if(in[i]==0) //这个点的入度为0就把它入队(队尾插入一个数,第一次插就是q[0],既是队头又是队尾)
        {
            q[++tt]=i;
        }
    }
    while(hh<=tt) //队列不为空的时候
    {
        int t=q[hh++]; //取出队头的点,它的入度为0
        
        for(int i=h[t];i!=-1;i=ne[i]) //遍历这个点的所有指向的点
        {
            int j=e[i]; 
            in[j]--;  //将指向的点入度-1
            if(in[j]==0)  //判断指向的点入度是否为0,如果为0就入队
            {
                q[++tt]=j;
            }
        }
    }
    return tt==n-1; //判断是否所有点都入队,有n个点,队列下标是从0开始的,所以第n个点的下标是n-1
    //如果所有的点都入队就说明他是一个有向无环图,是符合拓扑排序的
    //这里用数组模拟队列是为了能把这个拓扑序列保存下来,后面输出
}

这里我们用数组模拟队列是因为后面需要输出这个拓扑序列,而队列中入队的元素组合起来就是拓扑序列,如果用STL库里面的queue,删除之后就没法找到这些元素了,用数组模拟队列的删除只是将代表队头和队尾的下标改变了,实际上元素还存在队列里面。 

注意:拓扑排序的序列不唯一

示例代码:

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;

const int N=1e5+10;
int n,m;
int h[N],e[N],ne[N],idx; 
int q[N];  //队列,存放拓扑序列
int in[N];  //存放各个点的入度

void add(int a,int b) //从a到b的边
{
    e[idx]=b;
    ne[idx]=h[a];
    h[a]=idx;
    idx++;
}

bool topsort()
{
    int hh=0,tt=-1;  //队头队尾
    
    for(int i=1;i<=n;i++) //点的编号是从1开始的,从头开始找入度为0的点
    {
        if(in[i]==0) //这个点的入度为0就把它入队(队尾插入一个数,第一次插就是q[0],既是队头又是队尾)
        {
            q[++tt]=i;
        }
    }
    while(hh<=tt) //队列不为空的时候
    {
        int t=q[hh++]; //取出队头的点,它的入度为0
        
        for(int i=h[t];i!=-1;i=ne[i]) //遍历这个点的所有指向的点
        {
            int j=e[i]; 
            in[j]--;  //将指向的点入度-1
            if(in[j]==0)  //判断指向的点入度是否为0,如果为0就入队
            {
                q[++tt]=j;
            }
        }
    }
    return tt==n-1; //判断是否所有点都入队,有n个点,队列下标是从0开始的,所以第n个点的下标是n-1
    //如果所有的点都入队就说明他是一个有向无环图,是符合拓扑排序的
    //这里用数组模拟队列是为了能把这个拓扑序列保存下来,后面输出
}

int main()
{
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    cin>>n>>m;
    memset(h,-1,sizeof(h));  //领接表初始化
    
    while(m--)
    {
        int a,b;
        cin>>a>>b;
        add(a,b);  //增加一条从a到b的边
        in[b]++;
    }
    
    if(!topsort()) //如果拓扑序列不存在就输出-1
    {
        puts("-1");
    }
    else
    {
        for(int i=0;i<n;i++)
        {
            cout<<q[i]<<" "; //按顺序输出它的拓扑序列
        }
        cout<<endl;
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值