DSAA之图论双连通及割点问题(十一)

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 lowest low(w) among all tree edges (v, w)
      • the third way is to choose some tree edges and
        possibly a back edge.
  • Any other vertex v is an articulation point if and only if v has some child w such that low(w) 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 | )

4. 最后

  留下了个问题,为什么low(w) num(v)时,当前节点为割点?这个问题,暂时笔者当算法结论记住。

猜你喜欢

转载自blog.csdn.net/lovestackover/article/details/80715198