这是我参与11月更文挑战的第1天,活动详情查看2021最后一次更文挑战
前言
这是我写的第一篇leetcode算法题题解,写题解记录旨在将自己做题和学习算法题思路的过程写出来,同时根据费曼学习法,将自己对题目的理解做一次输出,以此达到更好掌握知识的目的。
介绍
拓扑排序的定义
- 拓扑排序属于图论中的一种场景
- 拓扑排序即Topological Sort,它指的是一个有向无环图(DAG,即Directed Acyclic Graph)所有顶点满足一定条件的线性序列。
- 拓扑排序本身不是一个算法的名称,指代的是所有满足条件的序列。
- 有向图的前提保证了其至少存在一个拓扑排序,但是一个有向图的拓扑排序的个数可能有多个。
最基本拓扑排序题目的场景和思路
题目中会给出一系列约束关系,即将题中的元素抽象成图中的顶点后,题干中的条件会约束一系列点Ai在Bi之前发生,最终想要求的是元素排序的顺序。
拓扑排序的两种解法思路
Kahn算法
Kahn算法利用了DAG有向无环图的性质,即:
- 初始时一定有入度为0的点(即初始无环)
- 在每次删掉入度为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的题目都过一遍,了解更复杂、或变种的拓扑排序系列题目的解法。