拓扑排序--思路介绍+图解模拟+详细代码注释

有向图的拓扑序列

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


啥是拓扑排序?

  1. 一个有向图,如果图中有入度为 0 的点,就把这个点删掉,同时也删掉这个点所连的边。

  2. 一直进行上面出处理,如果所有点都能被删掉,则这个图可以进行拓扑排序。

举例子

在这里插入图片描述

开始时,图是这样的状态,发现A的入度为 0,所以删除AA上所连的边,结果如下图:

在这里插入图片描述

这时发现B的入度为 0,C的入度为 0,所以删除BB上所连的边、CC上所连的边,结果如下图:

在这里插入图片描述

这时发现发现D的入度为 0,所以删除DD上所连的边(如果有就删),结果如下图:
在这里插入图片描述

这时整个图被删除干净,所有能进行拓扑排序。

解题思路

  1. 首先记录各个点的入度

  2. 然后将入度为 0 的点放入队列

  3. 将队列里的点依次出队列,然后找出所有出队列这个点发出的边,删除边,同事边的另一侧的点的入度 -1。

  4. 如果所有点都进过队列,则可以拓扑排序,输出所有顶点。否则输出-1,代表不可以进行拓扑排序。


代码

CPP代码

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 100010;
int e[N], ne[N], idx;//邻接表存储图
int h[N];
int q[N], hh = 0, tt = -1;//队列保存入度为0的点,也就是能够输出的点,
int n, m;//保存图的点数和边数
int d[N];保存各个点的入度

void add(int a, int b){
    e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

void topsort(){
    for(int i = 1; i <= n; i++){//遍历一遍顶点的入度。
        if(d[i] == 0)//如果入度为 0, 则可以入队列
            q[++tt] = i;
    }
    while(tt >= hh){//循环处理队列中点的
        int a = q[hh++];
        for(int i = h[a]; i != -1; i = ne[i]){//循环删除 a 发出的边
            int b = e[i];//a 有一条边指向b
            d[b]--;//删除边后,b的入度减1
            if(d[b] == 0)//如果b的入度减为 0,则 b 可以输出,入队列
                q[++tt] = b;
        }
    }
    if(tt == n - 1){//如果队列中的点的个数与图中点的个数相同,则可以进行拓扑排序
        for(int i = 0; i < n; i++){//队列中保存了所有入度为0的点,依次输出
            cout << q[i] << " ";
        }
    }
    else//如果队列中的点的个数与图中点的个数不相同,则可以进行拓扑排序
        cout << -1;//输出-1,代表错误
}


int main(){
    cin >> n >> m;//保存点的个数和边的个数
    memset(h, -1, sizeof h);//初始化邻接矩阵
    while (m -- ){//依次读入边
        int a, b;
        cin >> a >> b;
        d[b]++;//顶点b的入度+1
        add(a, b);//添加到邻接矩阵
    }
    topsort();//进行拓扑排序
    return 0;
}

Java 代码

import java.util.*;
public class Main{
    static int N =  100010,n,m,hh,tt,idx;
    static int[] e = new int[N],ne = new int[N],h = new int[N];
    static int[] q = new int[N];
    static int[] d = new int[N];
    public static void add(int a,int b){
        e[idx] = b;
        ne[idx] = h[a];
        h[a] = idx++;

    }
    public static boolean bfs(){
        hh = 0 ; tt = -1;
        for(int i = 1 ; i <= n ; i ++ ){ 
            if(d[i] == 0){     //首先将所有入度为0的点全部插入q队列中
                q[++tt] = i;
            }
        }
        while(hh <= tt){
            int t = q[hh++]; //拿出队头
            for(int i = h[t] ; i != -1; i = ne[i]){ //遍历一下队列中所有的边
                int s = e[i]; //然后将这个边拿出来
                d[s] -- ; //将这个边的入度数减1
                if(d[s] == 0){
                    q[++tt] = s; //如果减完之后s的入度数为0;就将他插入队列中
                }       
            }
        }
        return tt == n - 1; //最后返回,如果队列中的tt等于n-1个数,说明就是正确的的
    }
    public static void main(String[] args){
        Scanner scan = new Scanner(System.in);
        n = scan.nextInt();
        m = scan.nextInt();
        for(int i = 0 ; i < N ; i ++ ){
            h[i] = -1; 
        }
        while(m -- > 0){
            int a = scan.nextInt();
            int b = scan.nextInt();
            add(a,b);
            d[b] ++;
        }

        if(bfs()){
            //队列刚好队头删除的点就是我们的拓扑序列,因为我们只是将hh往后面移动,但是它具体前面的值还在,直接输出就行
            for(int i = 0 ; i < n; i ++ ){ 
                System.out.print(q[i] + " ");
            }
        }else{
            System.out.println("-1");
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值