DAG/拓扑排序
每两个节点之间都有一端指向另一端的边,这条边有可能有权重。比如上图从A到B要花1块钱。
图中没有环。也就是说不可能从一个节点出发再回到这个节点。
基础:拓扑排序
通过拓扑排序,DAG的节点是有先后次序的。如此遍历就保证不会漏掉我们要找的答案。最简单的例子是二叉树,父节点总是先于儿子节点和孙子节点。如此一来,无论是先序遍历(自顶向下)或者后序遍历(自底向上)都能给我们正确答案。
我们首先来介绍拓扑排序。拓扑排序有两种基本算法:深度优先,广度优先(Khan‘s Algorithm)。
基于深度优先的拓扑排序
def dfs(i):
vis.add(i)
for j in graph[i]:
if j not in vis:
dfs(j)
res.append(i) # 后序遍历
vis, res = set(), []
dfs(0)
return res[::-1]
其中深度优先函数dfs
相当于进行了后序遍历,所以最后我们把结果反转了一下。最终结果res
就是从左到右拓扑排序过的节点。
基于广度优先的拓扑排序
这个算法又叫做Khan's Algorithm,引用了入度的概念。一个节点的入度也就是有几个箭头指着它。这里我们讨论的是无环图,所以一个节点不可能指着自己。
# 计算初始入度
nodes = range(len(graph))
indegrees = {n: 0 for n in nodes}
for i in nodes:
for j in graph[i]:
indegrees[j] += 1
# 开始排序,先遍历队列中一直存着入度为0的节
queue = [n for n in n if indegrees[n]==0]
res = []
while queue:
res.append(queue.pop())
for j in graph[i]:
indegrees[j] -= 1
if indegrees[j]==0:
queue.append(j)
return res
到此,拓扑排序就完成了。接下来我们可以计算最值且不用担心错过答案了。
DAG的最短或最长路径
假设图中每条路径的花费永远是1(换句话说,我们的总花费=经过节点的个数),那么我们就可以说这个有向无环图是《无权值》的。
无论有否加权,DAG的最短或最长路径都可以用拓扑排序来计算。
下面我们用拓扑排序计算DAG最长路径。
nodes = range(len(graph))
dist = {n: float('inf') for n in nodes}
dist[start] = 0
queue = topoSortDFS(graph)
while queue:
i = queue.pop(0)
for j, w in graph[i]:
dist[j] = min(dist[j], w+dist.get(i, 0))
return dist
返回的dist是每个节点到最短路径。
其他情况的处理很简单:
如果是无权值的DAG,把
graph
中权重w设置成1即可。如果求最长路径,把
dist
初始成最小值,然后比较函数min
改为max
即可。
Last updated
Was this helpful?