数据结构与算法之拓扑排序

152 阅读5分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第34天,点击查看活动详情

✨欢迎关注🖱点赞🎀收藏⭐留言✒

🔮本文由京与旧铺原创,csdn首发!

😘系列专栏:算法学习

💻首发时间:🎞2022年12月29日🎠

🀄如果觉得博主的文章还不错的话,请三连支持一下博主哦

🎧作者是一个新人,在很多方面还做的不好,欢迎大佬指正,一起学习哦,冲冲冲


1.拓扑排序

拓扑排序(Topological Sorting),顾名思义,它是一种排序的算法,更准确的说,它是一种给图排序的算法,但是这张图呢,不是随便一张图都有拓扑排序,只有 有向无环图(DAG, Directed Acyclic Graph) 才存在拓扑排序序列,实质上它是对有向无环图的顶点排成一个线性序列,图中任意一对顶点u和v,若边<u,v>∈E(G),则u在线性序列中出现在v之前。

拓扑排序得到的序列满足以下特点:

  • 每个顶点出现且只出现一次。
  • 若存在一条从顶点 A 到顶点 B 的路径,那么在序列中顶点 A 出现在顶点 B 的前面。

例如: 0 这张图的拓扑排序后的序列为:1 2 3,根据图中的指向,结点1无论如何都会指向2或3,所以结点1会排在结点2和3的前面,结点2无论如何都会指向3,所以结点2排在结点3前,综合拓扑序列为1 2 3,当然,拓扑排序并不是唯一的。

如下面这一张图的拓扑排序序列为:1 5 6 2 4 8 3 7

1 当然不难发现,拓扑序列并不是唯一的,上图中相同颜色的节点调换位置,也还是一个拓扑排序,如5 1 6 2 4 8 3 7,1 5 2 6 4 8 3 7等。

那拓扑排序有什么作用,拓扑排序有什么意义呢?

举一个栗子,拓扑排序就相当于一个学习路线,比如学java路线,依次要学c,java,数据结构,数据库,servlet,spring,spring boot,mybatis等,但是这不是唯一的学习路线,因为学习顺序不是死的,你只要满足学习一门学科时,有必须的基础就可以了,就好比学数据结构前,你需要先学习一门编程语言,至于你先学c还是先学java是无所谓的,但是编程语言的学习一定在学数据结构之前。

2.拓扑排序实现

2.1实现思路

拓扑排序实现的思路就是BFS,即广度优先搜索,在谈论实现之前,我们先来了解一个概念,就是图的度。

对于图的度,分为出度和入度,出度就是结点指出边的条数(该结点指向其他结点的个数),入度就相反,就是指入边的条数(其他结点指向该结点的个数)。

我们知道图的实现可以使用邻接矩阵和邻接表实现,本文统一以邻接表的形式实现。

实现的思路与BFS类似:

  • 首先我们将所有入度为0的结点放入队列当中。
  • 第二步,取出队列中队头结点,遍历该结点所有指出的结点,并将指向遍历结点的边删除,即就是将入度减1
  • 第三步,删除边后,遍历结点的入度为0则将该结点入队。
  • 第四步,结点遍历完后,将队头结点存入顺序表或数组中(直接使用数组实现的队列,可不实现这一步,因为实现队列的数组就是一个记录顺序的表)。
  • 第五步,判断顺序表中元素个数是否与图中结点个数相同,相同表示存在拓扑排序,否则不存在。
  • 第六步,输出拓扑序列。

2.2实现代码

题目链接

848. 有向图的拓扑序列

给定一个 n 个点 m 条边的有向图,点的编号是 1 到 n,图中可能存在重边和自环。

请输出任意一个该有向图的拓扑序列,如果拓扑序列不存在,则输出 −1。

若一个由图中所有点构成的序列 A 满足:对于图中的每条边 (x,y),x 在 A 中都出现在 y 之前,则称 A 是该图的一个拓扑序列。

输入格式

第一行包含两个整数 n 和 m。

接下来 m 行,每行包含两个整数 x 和 y,表示存在一条从点 x 到点 y 的有向边 (x,y)。

输出格式

共一行,如果存在拓扑序列,则输出任意一个合法的拓扑序列即可。

否则输出 −1。

数据范围

1n,m1051≤n,m≤10^5

输入样例:

3 3
1 2
2 3
1 3

输出样例

1 2 3

解题思路: 本题就是实现拓扑排序,按照上述分析思路实现即可。

源代码: Java代码:

import java.util.*;


class Main {
    private static final int N = (int) 1e5 + 2333;
    
    //邻接表
    private static int[] h = new int[N], e = new int[N], en = new int[N];
    private static int[] d = new int[N];
    private static int idx = 0;
    //队列
    private static int[] q = new int[N];
    
    private static int n, m;
    private static void add(int a, int b) {
        e[idx] = b;
        en[idx] = h[a];
        h[a] = idx++;
    }
    
     private static boolean topsort() {
        //将入度为0的结点加入对列
        int head = 0;
        int tail = -1;
        for (int i = 1; i <= n; i++) {
            if (d[i] == 0) q[++tail] = i;
        }
        
        //bfs
        while (head <= tail) {
            int top = q[head++];
            for (int i = h[top]; i != -1; i = en[i]) {
                int j = e[i];
                d[j]--;
                if (d[j] == 0) q[++tail] = j;
            }
        }
        //System.out.println(tail + " " + n);
        return tail == n - 1;
    }
    
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        
        n = sc.nextInt();
        m = sc.nextInt();
        
        Arrays.fill(h, -1);
        
        while (m-- > 0) {
            int a = sc.nextInt();
            int b = sc.nextInt();
            
            add(a, b);
            d[b]++;
        }
        
        //如果存在拓扑序列就输出
        if (topsort()) {
            for (int i = 0; i < n; i++) {
                System.out.print(q[i] + " ");
            }
            System.out.println();
        } else {
            System.out.println(-1);
        }
    } 
}

C++代码:

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = (int) 1e5 + 2333;

int n, m;
//邻接矩阵
int h[N], e[N], en[N];

//队列,入度
int q[N], d[N];

int idx = 0;

//添加边
void add(int a, int b) 
{
    e[idx] = b;
    en[idx] = h[a];
    h[a] = idx++;
}

//判断是否有拓扑序列
bool topsort() 
{
    int head = 0, tail = -1;
    //将度为0的点加入队列
    for (int i = 1; i <= n; i++)
    {
        if (!d[i]) q[++tail] = i;
    }
    
    while (head <= tail) 
    {
        int top = q[head++];
        for (int i = h[top]; i != -1; i = en[i]) 
        {
            int j = e[i];
            d[j]--;
            if (d[j] == 0) q[++tail] = j;
        }
    }
    return tail == n - 1;
}

int main() 
{
    cin >> n >> m;
    memset(h, -1, sizeof(h));
    //添加所有的边
    while (m-- > 0) 
    {
        int a, b;
        cin >> a >> b;
        
        add(a, b);
        //b入度加1
        d[b]++;
    }
    
    if (topsort()) 
    {
        //最终队列中元素的顺序就是一个拓扑序列(拓扑序列不唯一)
        for (int i = 0; i < n; i++) 
        {
            cout << q[i] << " ";
        }
        cout << endl;
    } else 
    {
        cout << "-1" << endl;
    }
   
    return 0;
}