文章目录
前言
Datawhale十月组队学习,LeetCode算法
一、拓扑排序
拓扑排序(Topological Sorting):一种对有向无环图(DAG)的所有顶点进行线性排序的方法,使得图中任意一点 u 和 v,如果存在有向边 <u, v>,则 u必须在 v 之前出现。对有向图进行拓扑排序产生的线性序列称为满足拓扑次序的序列,简称拓扑排序。
图的拓扑排序是针对有向无环图(DAG)来说的,无向图和有向有环图没有拓扑排序,或者说不存在拓扑排序。
二、拓扑排序的实现方法
1.Kahn 算法
代码如下:
import collections
class Solution:
# 拓扑排序,graph 中包含所有顶点的有向边关系(包括无边顶点)
def topologicalSortingKahn(self, graph: dict):
indegrees = {u: 0 for u in graph} # indegrees 用于记录所有顶点入度
for u in graph:
for v in graph[u]:
indegrees[v] += 1 # 统计所有顶点入度
# 将入度为 0 的顶点存入集合 S 中
S = collections.deque([u for u in indegrees if indegrees[u] == 0])
order = [] # order 用于存储拓扑序列
while S:
u = S.pop() # 从集合中选择一个没有前驱的顶点 0
order.append(u) # 将其输出到拓扑序列 order 中
for v in graph[u]: # 遍历顶点 u 的邻接顶点 v
indegrees[v] -= 1 # 删除从顶点 u 出发的有向边
if indegrees[v] == 0: # 如果删除该边后顶点 v 的入度变为 0
S.append(v) # 将其放入集合 S 中
if len(indegrees) != len(order): # 还有顶点未遍历(存在环),无法构成拓扑序列
return []
return order # 返回拓扑序列
def findOrder(self, n: int, edges):
# 构建图
graph = dict()
for i in range(n):
graph[i] = []
for u, v in edges:
graph[u].append(v)
return self.topologicalSortingKahn(graph)
这个算法类似于广度优先搜索BFS,利用循环查出数据在graph中的出现次数,在利用循环从集合S中入度为零的节点开始访问,将访问过的数据存入队列中,循环过的记录出现过数据的值减一,若值为零,则存入集合S中,直到集合S为空。若这组数据为拓补排序,则数据的访问与基于队列实现的广度优先搜索算法类似,都是访问当前节点指向的所有节点,再访问记录的下一个节点,但这个有所不同,在访问下一个节点时,这个算法是按照graph这组数据的顺序来访问的,比广度优先搜索好理解。这个算法判断是否有环,是判断数据是否访问完全。
2.基于 DFS 实现拓扑排序算法
代码如下:
import collections
class Solution:
# 拓扑排序,graph 中包含所有顶点的有向边关系(包括无边顶点)
def topologicalSortingDFS(self, graph: dict):
visited = set() # 记录当前顶点是否被访问过
onStack = set() # 记录同一次深搜时,当前顶点是否被访问过
order = [] # 用于存储拓扑序列
hasCycle = False # 用于判断是否存在环
def dfs(u):
nonlocal hasCycle
if u in onStack: # 同一次深度优先搜索时,当前顶点被访问过,说明存在环
hasCycle = True
if u in visited or hasCycle: # 当前节点被访问或者有环时直接返回
return
visited.add(u) # 标记节点被访问
onStack.add(u) # 标记本次深搜时,当前顶点被访问
for v in graph[u]: # 遍历顶点 u 的邻接顶点 v
dfs(v) # 递归访问节点 v
order.append(u) # 后序遍历顺序访问节点 u
onStack.remove(u) # 取消本次深搜时的 顶点访问标记
for u in graph:
if u not in visited:
dfs(u) # 递归遍历未访问节点 u
if hasCycle: # 判断是否存在环
return [] # 存在环,无法构成拓扑序列
order.reverse() # 将后序遍历转为拓扑排序顺序
return order # 返回拓扑序列
def findOrder(self, n: int, edges):
# 构建图
graph = dict()
for i in range(n):
graph[i] = []
for v, u in edges:
graph[u].append(v)
return self.topologicalSortingDFS(graph)
基于深度优先搜素DFS算法实现,在学习过深度优先搜素基于递归实现之后,在代码中最重要以及难点是这一组数据是否是有向无环的,即判断在深度搜索一支数据串时 ,没有形成环;次要难点是深度优先搜素的递归调用的过程中,若在某一节点的下一个节点都被访问过,则要访问这一节点的上一节点。我认为是若这一节点的下一节点都被访问过,则结束这一次递归调用,直接执行上一次递归调用的剩余循环。
总结
我认为拓扑排序是一组有向无环的线性结构。
在kahn算法中利用类似广度优先搜索的方法,记录数据存在的次数,看最后队列中存在的数据是否访问完的,来判断是否存在环。
在DFS算法中利用深度优先搜素基于递归实现来访问数据,在同一递归中判断该节点是否被访问过,来判断是否有环。