拓扑排序和强连通分量
我这里参考的书籍是 《[数据结构与算法分析_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