1. 定义
- A connected undirected graph is biconnected if there are no vertices whose removal disconnects the rest of the graph.
- If a graph is not biconnected, the vertices whose removal would disconnect the graph are known as articulation points.
在理解下面的算法前,再次记忆双连通是指不存在割点的无向连通图。
2. 解决算法
- Depth-first search provides a linear-time algorithm to find all articulation points in a connected graph.
- First, starting at any vertex, we perform a depth first search and number the nodes as they are visited. For each vertex v, we call this preorder number num(v).
这也是下面的算法assign_num所做的事情,非常简单就是图的DFS遍历及给每个节点赋值
- Then, for every vertex v in the depth-first search spanning tree, we compute the lowest-numbered vertex, which we call low(v).
之前说过不太理解DFS生成树的coding办法,其实DFS生成树是DFS搜索过程的反映,只要在DFS搜索过程中做相应的处理就相当于后序或者中序遍历DFS生成树
- We can efficiently compute low by performing a postorder traversal of the depth-first spanning tree.
- By the definition of low, low(v) is the minimum of
以下三者中的最小值,所以每条都要计算
- num(v)
- The first condition is the option of taking no edges
- the lowest num(w) among all back edges (v, w)
- the second way is to choose no tree edges and a back edge
就是所有背向边中最小的num(v)
- the second way is to choose no tree edges and a back edge
- the lowest low(w) among all tree edges (v, w)
- the third way is to choose some tree edges and
possibly a back edge.
- the third way is to choose some tree edges and
- num(v)
- Any other vertex v is an articulation point if and only if v has some child w such that low(w)
≥
\geq
≥ num(v).
这里是最关键的割点的判断条件
- Notice that this condition is always satisfied at the root; hence the need for a special test.
下面的伪代码由DSAA给出,将所有的步骤整合到一起,在DFS框架中给节点编号,并在调用递归之后判断每个节点的low(),同时判断是否有割点出现。这种编程方式将前序遍历和后续遍历整合到一起,值得学习。
3. 伪代码
void find_art( vertex v ){
vertex w;
visited[v] = TRUE;
/* Rule 1 同时也给每个节点一个前序编号*/
low[v] = num[v] = counter++;
for each w adjacent to v{
{/* forward edge 没有访问的节点,一定是当前节点的子代*/
if( !visited[w] ){
//标记当前节点w的父代为v
parent[w] = v;
find_art( w );
//通过上面的find_art(w)已经获得low(w)值,判断是否w-v满足割点性质,此时是后序遍历。使用nums[v] !=1 排除root节点
if( nums[v] != 1 &&low[w] >= num[v] )
printf ( "%v is an articulation point\n", v );
//Rule 3 更新当前节点的low值
low[v] = min( low[v], low[w] );
}
/* back edge 下面解释*/
else if( parent[v] != w )
//Rule 2 更新当前节点的low值
low[v] = min( low[v], num[w] );
}
}
上面伪代码中最后的else if( parent[v] != w )
需要重点考虑下,因为无向图边都是双向的,所以在w已经访问过的时候,一种可能是在之前w-v已经考虑过该边了,而此时再将v-w当成背向边将导致错误,所以这里需要判断下。借助下图可以很容易理解:(比如:CD边与DC边)
时间复杂度
由于上面的整体框架还是DFS,遍历每一个边和节点一次,所以整体上的时间复杂度为 O ( ∣ E ∣ + ∣ V ∣ ) O(|E|+|V|) O(∣E∣+∣V∣)。
4. 最后
留下了个问题,**为什么low(w) ≥ \geq ≥ num(v)时,当前节点为割点?**这个问题,暂时笔者当算法结论记住。