「LeetCode」拓扑排序系列(一)

629 阅读3分钟

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

前言

这是我写的第一篇leetcode算法题题解,写题解记录旨在将自己做题和学习算法题思路的过程写出来,同时根据费曼学习法,将自己对题目的理解做一次输出,以此达到更好掌握知识的目的。

介绍

拓扑排序的定义

  • 拓扑排序属于图论中的一种场景
  • 拓扑排序即Topological Sort,它指的是一个有向无环图(DAG,即Directed Acyclic Graph)所有顶点满足一定条件的线性序列。
  • 拓扑排序本身不是一个算法的名称,指代的是所有满足条件的序列。
  • 有向图的前提保证了其至少存在一个拓扑排序,但是一个有向图的拓扑排序的个数可能有多个。

最基本拓扑排序题目的场景和思路

题目中会给出一系列约束关系,即将题中的元素抽象成图中的顶点后,题干中的条件会约束一系列点Ai在Bi之前发生,最终想要求的是元素排序的顺序。

拓扑排序的两种解法思路

Kahn算法

Kahn算法利用了DAG有向无环图的性质,即:

  1. 初始时一定有入度为0的点(即初始无环)
  2. 在每次删掉入度为0的点和其相邻的边之后,剩下的点中一定依然存在入度为0的点(即删掉部分边之后依然不存在环) 重复上述操作,删掉入度为0的点,即可完成对所有点的删除。

基于DFS和栈

对图的每个顶点进行一次深度优先搜索之后,每个节点进行回溯的时候,其相邻节点可以保证都被搜索过,在这时将其存入一个栈中即可以保证每个节点拓扑排序的正确性。

LeetCode拓扑排序基础题

207. 课程表

解法:

基于Kahn算法的解法

class Solution:
    def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
        if len(prerequisites) == 0:
            return True
        adj = [[] for _ in range(numCourses)]
        prerequisiteCount = len(prerequisites)
        indegree = [0] * numCourses
        for prerequisite in prerequisites:
            adj[prerequisite[1]].append(prerequisite[0])
            indegree[prerequisite[0]] += 1
        queue = []
        for i in range(numCourses):
            if indegree[i] == 0:
                queue.append(i)
        while len(queue):
            front = queue.pop(0)
            for tail in adj[front]:
                indegree[tail] -= 1
                prerequisiteCount -= 1
                if indegree[tail] == 0:
                    queue.append(tail)
        return prerequisiteCount == 0

基于DFS和栈的解法

class Solution:
    def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
        if len(prerequisites) == 0:
            return True
        adj = [[] for _ in range(numCourses)]
        for prerequisite in prerequisites:
            adj[prerequisite[1]].append(prerequisite[0])
        visit = [0] * numCourses
        def dfs(i):
            visit[i] = 1
            for tail in adj[i]:
                if visit[tail] == 1:
                    return False
                elif visit[tail] == 0:
                    result = dfs(tail)
                    if result == False:
                        return False
            visit[i] = -1
            return True
        for i in range(numCourses):
            if visit[i] < 0:
                continue
            result = dfs(i)
            if result == False:
                return False
        return True

210. 课程表 II

解法:

基于Kahn算法的解法

class Solution:
    def findOrder(self, numCourses: int, prerequisites: List[List[int]]) -> List[int]:
        if len(prerequisites) == 0:
            return [i for i in range(numCourses)]
        adj = [[] for _ in range(numCourses)]
        prerequisiteCount = len(prerequisites)
        indegree = [0] * numCourses
        for prerequisite in prerequisites:
            adj[prerequisite[1]].append(prerequisite[0])
            indegree[prerequisite[0]] += 1
        queue = []
        for i in range(numCourses):
            if indegree[i] == 0:
                queue.append(i)
        pInd = 0
        while pInd < len(queue):
            front = queue[pInd]
            pInd += 1
            for tail in adj[front]:
                indegree[tail] -= 1
                prerequisiteCount -= 1
                if indegree[tail] == 0:
                    queue.append(tail)
        if pInd == numCourses:
            return queue
        else:
            return []

基于DFS和栈的解法

class Solution:
    def findOrder(self, numCourses: int, prerequisites: List[List[int]]) -> List[int]:
        if len(prerequisites) == 0:
            return [i for i in range(numCourses)]
        adj = [[] for _ in range(numCourses)]
        for prerequisite in prerequisites:
            adj[prerequisite[1]].append(prerequisite[0])
        visit = [0] * numCourses
        stack = []
        def dfs(i):
            visit[i] = 1
            for tail in adj[i]:
                if visit[tail] == 1:
                    return False
                elif visit[tail] == 0:
                    result = dfs(tail)
                    if result == False:
                        return False
            visit[i] = -1
            stack.append(i)
            return True
        for i in range(numCourses):
            if visit[i] < 0:
                continue
            result = dfs(i)
            if result == False:
                return []
        stack.reverse()
        return stack

后记

在拓扑排序系列(一)中,我们了解了两种基本拓扑排序题目(也可以说是模板题)的解法,后续我们会将LeetCode中带拓扑排序Tag的题目都过一遍,了解更复杂、或变种的拓扑排序系列题目的解法。