拓扑排序和强连通分量
我这里参考的书籍是 《[数据结构与算法分析_Java语言描述(第2版)].韦斯》,甚至代码里面的例子都是书里面的。
拓扑排序的一种算法是:
- 初始化计算每个节点的入度
- 不断地寻找入度为0的节点,记录下来 `res.append(x)`
- 然后删除这个节点,并且减少它连接节点的入度
- 直到所有节点都被删除,那么 `res` 就是一个拓扑排序。
这个算法非常直观,但是有点开销问题就是需要维护入度这个数据结构(可以使用堆实现),此外也不需要使用递归。 另外一个拓扑排序的实现就是,对有向图进行DFS,先访问孩子然后在访问自己。然后这个顺序的逆序也是一个拓扑排序
def topsort_dfs(G: Graph):
# 使用满足拓扑排序要求的顺序进行DFS,之后逆序输出
# 这些节点就满足拓扑排序要求
visited = set()
orders = []
n = len(G)
def fn(v):
visited.add(v)
for x in range(n):
if G.mat[v][x] != 0 and x not in visited:
fn(x)
orders.append(v)
for v in range(n):
if v in visited:
continue
fn(v)
return orders[::-1]
所谓强连通分量(strongly connected component, scc)就是一个图中,对于所有节点对(x,y), 可以从x到y, 也可以从y到x. 强连通分量的求解依赖于上面的拓扑排序,并且可以求解到各个强连通分量。假设我们有图G
- 对图G计算拓扑排序
- 按照这个拓扑顺序对G'进行DFS
其中G'是G的反向图。每次DFS得到的一个component就是一个scc
def find_scc(G: Graph):
# 首先DFS对G里面每个节点进行拓扑排序
# 然后按照拓扑顺序,对G'(G的反向图)做DFS.
# 每次得到的一个component就是strongly connected component.
n = len(G)
# 先求解拓扑序
visited = set()
orders = []
def dfs_fwd(v):
visited.add(v)
for x in range(n):
if G.mat[v][x] != 0 and x not in visited:
dfs_fwd(x)
orders.append(v)
for v in range(n):
if v not in visited:
dfs_fwd(v)
orders = orders[::-1]
# 基于拓扑序在反向图遍历
res = []
scc = set()
visited = set()
def dfs_back(v):
scc.add(v)
for x in range(n):
if G.mat[x][v] != 0 and x not in visited:
visited.add(x)
dfs_back(x)
for v in orders:
if v not in visited:
scc = set()
dfs_back(v)
res.append(scc)
return res