[路飞]前端算法——算法篇(一、排序算法): 拓扑排序

582 阅读4分钟

「这是我参与11月更文挑战的第6天,活动详情查看:2021最后一次更文挑战

前言

前端算法系列是我对算法学习的一个记录, 主要从常见算法数据结构算法思维常用技巧几个方面剖析学习算法知识, 通过LeetCode平台实现刻意练习, 通过掘金和B站的输出来实践费曼学习法, 我会在后续不断更新优质内容并同步更新到掘金、B站和Github, 以记录学习算法的完整过程, 欢迎大家多多交流点赞收藏, 让我们共同进步, daydayup👊

目录地址:目录篇

相关代码地址: Github

相关视频地址: 哔哩哔哩-百日算法系列

一、什么是拓扑排序

对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边<u,v>∈E(G),则u在线性序列中出现在v之前。

简单来说, 拓扑排序就是在不破环节点 先后顺序 的前提下, 将一个 有向无环图(DAG)拉成一条链的过程.

在这个过程中, 我们不能破环原有的先后顺序, 例如3到2是有明确的指向关系的, 我们不能将2放在3的前面.

v2-ecb3af795e137fb78582689b9270d28d_1440w.jpeg

二、如何实现拓扑排序

实现拓扑排序一般有深搜(DFS)广搜(BFS)两种方案, 其中比较经典的就是Kahn算法, 它是一种广搜算法, 利用队列实现.

在实现算法之前我们还需要了解一些 图算法 相关的知识:

度: 就是一个顶点相关联的边的数量
入度: 就是以某个顶点开始, 以当前顶点结束, 边的数量. (也就是有几个箭头指向该顶点)
出度: 就是以某个顶点结束, 以当前顶点开始, 边的数量. (也就是从该顶点延伸出几条线)

在明白了入度和出度的意思之后, 我们就可以开始梳理我们的思路

  1. 我们将 入度为0 的顶点放到队列中

  2. 更新图中顶点的,

  3. 因为操作的图是一个有向无环图, 在操作的过程中我们永远都能找到入度为0的顶点, 重复以上操作

/*
图的结构
    1.邻接矩阵(二维数组)
    2.邻接表(可变长度数组、链表)
*/

// leetcode 207.课程表
// https://leetcode-cn.com/problems/course-schedule/
/**
 * @param {number} numCourses
 * @param {number[][]} prerequisites
 * @return {boolean}
 */
var canFinish = function(numCourses, prerequisites) {
    let quenu = []
    let indeg = new Array(numCourses).fill(0)
    let echart = new Array(numCourses).fill(1).map(() => [])

    // 初始化 度 和 图
    for(let x of prerequisites) {
        // x[0] 表示终点
        // x[1] 表示起点
        indeg[x[0]] += 1
        echart[x[1]].push(x[0])
    }

    // 初识化 队列
    for(let i=0; i<numCourses; i++) {
        if (indeg[i] === 0) quenu.push(i)
    }

    // 循环遍历
    let cnt = 0
    let ans = new Array(numCourses)
    while(quenu.length) {
        let front = quenu.shift()
        ans[cnt++] = front
        
        for(let x of echart[front]) {
            indeg[x] -= 1
            if (indeg[x] === 0) {
                quenu.push(x)
            }
        }
    }
    return numCourses === cnt
};

三、拓扑排序有什么用

1.求拓扑序

对存在一定依赖关系的数据进行排序

获取拓扑顺序是拓扑排序最常用的功能, 它把一个有向无环图转化为一个线性序列.

拓扑排序同队列与栈一样, 应用十分广泛, 比如, 你一会要下楼去便利店买点东西, 去快递站拿个快递. 这里我们可以将其拆分为几个步骤:

  • 下楼
  • 去便利店
  • 买东西
  • 去快递站
  • 拿快递
  • 上楼

在这些步骤中, 是存在一定的依赖关系的, 比如我们要买东西首先需要到达便利店, 我们要拿快递必须先到达快递站

此外, 诸如向 启动优化, 软件安装, 课程学习等...各种问题的本质都是对存在一定依赖关系的数据进行一个排序, 从而得到最终的一个拓扑序.

2.是否有环判断

因为拓扑排序是针对 有向无环图(DAG)的排序, 我们可以通过是否能够得出拓扑排序来判断是否有环. 当我们使用广搜出现以下情况时, 我们就可以断定当前是有环的.

队列为空 && 得到的拓扑排序的点的数量 < 总的顶点数量