拓扑排序(Topological Sorting)通常用来解决依赖关系问题。
拓扑排序通常用来“排序”具有依赖关系的任务。
比如,如果用一个DAG图来表示一个工程,其中每个顶点表示工程中的一个任务,用有向边 表示在做任务 B 之前必须先完成任务 A。
故在这个工程中,任意两个任务要么具有确定的先后关系,要么是没有关系,绝对不存在互相矛盾的关系(即环路)。

例题: 210. 课程表 II
建完图直接跑拓扑排序,最后判断长度是否是n即可知道是否跑完。
这题和剑指 Offer II 113. 课程顺序是一样的
class Solution:
def findOrder(self, numCourses: int, prerequisites: List[List[int]]) -> List[int]:
indegree = [0] * numCourses
g = defaultdict(list)
for a,b in prerequisites:
g[b].append(a)
indegree[a] += 1
q = deque([i for i,v in enumerate(indegree) if v == 0])
ans = []
while q:
u = q.popleft()
ans.append(u)
for v in g[u]:
indegree[v] -= 1
if indegree[v] == 0:
q.append(v)
return ans if len(ans) == numCourses else []
链接: 207. 课程表
直接裸跑DAG
class Solution:
def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
g = [[] for _ in range(numCourses)]
indegree = [0] * numCourses
for v,u in prerequisites: # 注意题目反着给依赖
g[u].append(v)
indegree[v] += 1
q = deque([u for u,x in enumerate(indegree) if x==0])
visited = []
while q:
u = q.popleft()
visited.append(u)
for v in g[u]:
indegree[v] -= 1
if indegree[v] == 0:
q.append(v)
return len(visited) == numCourses
链接: 1462. 课程表 IV
这题其实是求有向图任意两点的连通性,数据范围100,floyd就能跑,码量也小。但是没有拓扑排序快。
fathers = [set() for _ in range(numCourses)]class Solution:
def checkIfPrerequisite(self, numCourses: int, prerequisites: List[List[int]], queries: List[List[int]]) -> List[bool]:
graph = defaultdict(list)
indegree = [0] * numCourses
for a,b in prerequisites:
graph[a].append(b)
indegree[b] += 1
q = deque([i for i in range(numCourses) if indegree[i] == 0])
fathers = [set() for _ in range(numCourses)]
while q:
u = q.popleft()
for v in graph[u]:
fathers[v].add(u)
fathers[v].update(fathers[u])
indegree[v] -= 1
if indegree[v] == 0:
q.append(v)
return [ u in fathers[v] for u,v in queries]
这题给出每个材料和菜的依赖关系,给出一些成品菜,因此可以从中途节点开始遍历,对联初始化为给定的节点。
参考链接: [英雄星球六月集训LeetCode解题日报] 第30日 拓扑排序
class Solution:
def findAllRecipes(self, recipes: List[str], ingredients: List[List[str]], supplies: List[str]) -> List[str]:
n = len(recipes)
indegree = defaultdict(int)
graph = defaultdict(list)
for i in range(n):
recipe = recipes[i]
indegree[recipe] += len(ingredients[i])
for ingredient in ingredients[i]:
graph[ingredient].append(recipe)
indegree[ingredient] += 0
q = deque(supplies)
visited =set(supplies)
while q:
u = q.popleft()
for v in graph[u]:
indegree[v] -= 1
if indegree[v] == 0:
visited.add(v)
q.append(v)
return [r for r in recipes if r in visited]
参考链接: [英雄星球六月集训LeetCode解题日报] 第30日 拓扑排序
class Solution:
def sequenceReconstruction(self, nums: List[int], sequences: List[List[int]]) -> bool:
n = len(nums)
g = defaultdict(list)
indegree = [0]*(n+1)
for seq in sequences:
for i in range(1,len(seq)):
g[seq[i-1]].append(seq[i])
indegree[seq[i]] += 1
visited = []
q = deque([i for i in range(1,n+1) if indegree[i] == 0])
while q:
if len(q) >= 2:
return False
u = q.popleft()
visited.append(u)
for v in g[u]:
indegree[v] -= 1
if indegree[v] == 0:
q.append(v)
return visited == nums
参考链接: [英雄星球六月集训LeetCode解题日报] 第30日 拓扑排序
手写
class Solution:
def alienOrder(self, words: List[str]) -> str:
n = len(words)
cs = set(''.join(words))
graph = collections.defaultdict(list)
indegree = [0]*26
for word1, word2 in pairwise(words):
l,r = len(word1),len(word2)
k = 0
# 规则1, 在第一个不同字母处,如果 s 中的字母在这门外星语言的字母顺序中位于 t 中字母之前,那么 s 的字典顺序小于 t 。
while k < min(l,r):
c1,c2 = word1[k],word2[k]
if c1 != c2:
graph[c1].append(c2)
indegree[ord(c2)-ord('a')] += 1
break
k += 1
# 规则2,如果前面 min(s.length, t.length) 字母都相同,那么 s.length < t.length 时,s 的字典顺序也小于 t 。
if k == r and l > r:
return ''
ans = ''
# 拓扑排序
q = deque([chr(ord('a')+i) for i,v in enumerate(indegree) if v==0 and chr(ord('a')+i) in cs])
visited = []
while q:
u = q.popleft() # 取出队列中的点
visited.append(u)
for v in graph[u]:
indegree[ord(v)-ord('a')] -= 1 # u遍历到的所有点v,入度-1
if indegree[ord(v)-ord('a')] ==0: # 如果v的入度变成0,则放入队列。
# visited.append(v)
q.append(v)
if len(visited) == len(cs):
return ''.join(visited)
return ''
from graphlib import TopologicalSorter
class Solution:
def alienOrder(self, words: List[str]) -> str:
n = len(words)
cs = set(''.join(words))
from graphlib import TopologicalSorter
ts = TopologicalSorter()
for c in cs:
ts.add(c)
for word1, word2 in combinations(words,2):
l,r = len(word1),len(word2)
k = 0
# 规则1, 在第一个不同字母处,如果 s 中的字母在这门外星语言的字母顺序中位于 t 中字母之前,那么 s 的字典顺序小于 t 。
while k < min(l,r):
c1,c2 = word1[k],word2[k]
if c1 != c2:
ts.add(c2, c1)
break
k += 1
# 规则2,如果前面 min(s.length, t.length) 字母都相同,那么 s.length < t.length 时,s 的字典顺序也小于 t 。
if k == r and l > r:
return ''
try:
return ''.join(ts.static_order())
except:
return ''