寻找无向图中的点双连通分量
本文介绍了基于Tarjan算法改进的在无向图中寻找点双连通分量的一种算法。
这篇文章会介绍寻找无向图中的点双连通分量算法,由于要用到Tarjan算法相关的前辈知识,建议先阅读这篇介绍Tarjan算法的文章再来阅读。
双连通分量分为点双连通和边双连通。
点双连通是指这个一个无向图的极大子图,删掉任意一个节点后,子图仍然连通。边双连通分量定义类似,就是改为去掉任意一条边。
一般在实际应用中,点双连通分量最常用。
寻找双连通分量算法最核心的判定法则:
在DFS过程中,对于边(u, v), low[v] >= dfn[u]
如何理解这个条件呢?
我们知道low[v]是指顺着v往下走可以到达的最早的节点。如果 low[v] < dfn[v]。就意味着存在一个环,从v点出发可以到达v的上游。
而对于边(u, v),如果low[v] >= dfn[u], 就说明 v 无法跳过 u 达到更早的祖先,这个时候,u就是一个潜在的割点。一旦u消失,那么v这一部分就和上游节点隔离了。
由此可以给出对应的算法框架:
Algorithm: Find_vBCC_Components(graph)
Input: a undirected graph stored in adj
Output: a array BCC_List where each item is a set of vertices in same BCC
Fn Find_vBCC_Components(graph)
init an empty array BCC_List
init dfn[], low[] (all set to 0 )
init a stack Q
init timer=0
for each vertex u in G:
if (dfn[u]==0):
DFS_vBCC({G,u,NULL}) // {graph, vertex, parentVetex}
endif
endfor
return BCC_List
Procedure DFS_vBCC({G,u,p})
timer++
dfn[u]=low[u]=timer
for each vertex v in G.adj[u]:
if(v==p):
continue // 跳过指向父节点的边
endif
if(dfn[v]==0):
S.push(edge(u, v))
DFS_vBCC({G,v,u})
low[u]=min(low[u], low[v])
if(low[v] >= dfn[u]):
Start new BCC
Repeat:
edge(m,n) = S.pop()
append m,n to BCC
Until: (m,n)=(u,v)
BCC_List.append(BCC)
endif
else if (dfn[v] < dfn[u]): // 找到一条反向边
S.push(edge(u, v))
low[u]=min(dfn[v],low[u])
endif
endfor
这里的DFS和 Tarjan 算法有个关键的差别:栈中存储的是边,而不是点。
这是因为一个点可能属于多个双连通分量,如果存储点的话,构建新BCC的最后一步就会将点从栈中弹出,其他双连通分量就无法找到了。
我们可以以一个例子来理解这个过程:
E -- D
| |
A -- B -- C
从A点开始DFS,栈中逐渐加入 A, B, C, D, E.
当处理E时,会发现B点是祖先节点,所以会更新自己的low值。递归回溯的过程中,D, C, B的low值都会被更新,并且D, C 过程中low值都比自己的dfn值要小,不会有任何处理。
但是到了B点,会发现low值和dfn值相等,于是就找到了一个双连通分量。他会将这几条边弹出栈:(E,B) (D, E) (C, D) (B, C)。这就找到了一个双连通分量。
然后退回到A点,发现low值和dfn值又相等,于是又得到一个双连通分量{A, B}
这样,就对双连通分量的算法运行过程有了一个比较直观的理解。