用于查找图的关节点或切割顶点的算法的说明

Explanation of Algorithm for finding articulation points or cut vertices of a graph

我搜索过网络,找不到任何解释的DFS算法,以寻找一个图的所有关节顶点。甚至没有wiki页面。

通过阅读,我从这里了解了基本事实。PDF

在每个节点上都有一个变量,它实际上是在查看后缘,并找到靠近根节点的最接近和最上的节点。在处理完所有边缘之后,就会找到它。

但我不理解如何在执行DFS期间在每个节点上找到这个向下和向上的变量。这个变量究竟在做什么?

请解释一下算法。

谢谢。


寻找关节顶点是DFS的一个应用。

简而言之,

  • 在图形上应用DFS。获取DFS树。
  • 较早访问的节点是那些节点的"父节点",这些节点由它访问并随后访问。
  • 如果节点的任何子节点没有指向其父节点的任何祖先的路径,则意味着删除此节点将使此子节点从图形中分离。
  • 有一个例外:树的根。如果它有多个孩子,那么它是一个发音点,否则不是。
  • 点3基本上意味着这个节点是一个连接点。

    现在对于一个孩子来说,这个通往节点祖先的路径是通过它或它的任何一个子节点的后缘。

    所有这些都在这个PDF中得到了很好的解释。


    我将尝试对该算法的工作原理进行直观的理解,并给出输出BI组件和网桥的带注释的伪代码。

    实际上很容易为发音点开发一个蛮力算法。只需取出一个顶点,在一个图上运行bfs或df。如果它保持连接,则顶点不是关节点,否则它是。这将在O(V(E+V)) = O(EV)时间内运行。挑战在于如何在线性时间内做到这一点(即O(E+V))。

    连接点连接两个(或更多)子图。这意味着从一个子图到另一个子图没有边。所以假设您在这些子图中的一个子图中并访问它的节点。访问节点时,标记它,然后使用某个可用边移动到下一个未标记的节点。当你这样做的时候,你怎么知道你在同样的子图中?这里的见解是,如果您在同一个子图中,您最终会在访问未标记的节点时通过边看到标记的节点。这称为后缘,表示有一个循环。一旦找到一个后缘,您就可以确信,通过该标记节点到您当前访问的节点的所有节点都是同一子图的一部分,并且中间没有连接点。如果没有看到任何后缘,那么到目前为止访问的所有节点都是连接点。

    因此,我们需要一个访问顶点的算法,并将后缘目标之间的所有点标记为当前访问的节点,就像在同一个子图中一样。很明显,子图中可能有子图,所以我们需要选择迄今为止最大的子图。这些子图称为BI组件。我们可以通过给每个双组件分配一个ID来实现这个算法,这个ID被初始化为我们迄今为止访问的顶点数量的一个计数。稍后,当我们找到后边缘时,我们可以将bi-compinent ID重置为迄今为止发现的最低值。

    我们显然需要两次传球。在第一个步骤中,我们想知道我们可以从每个顶点通过后缘看到哪个顶点(如果有的话)。在第二个步骤中,我们希望以相反的方向访问顶点,并收集最小的双组件ID(即从任何子代访问的最早祖先)。DFS自然适合这里。在DFS中,我们先向下,然后再向上返回,以便在单个DFS遍历中完成上述两个过程。

    现在不用再多费吹灰之力,这里是伪代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    time = 0
    visited[i] = false for all i
    GetArticulationPoints(u)
        visited[u] = true
        u.st = time++
        u.low = v.st    //keeps track of highest ancestor reachable from any descendants
        dfsChild = 0    //needed because if no child then removing this node doesn't decompose graph
        for each ni in adj[i]
            if not visited[ni]
                GetArticulationPoints(ni)
                ++dfsChild
                parents[ni] = u
                u.low = Min(u.low, ni.low)  //while coming back up, get the lowest reachable ancestor from descendants
            else if ni <> parent[u] //while going down, note down the back edges
                u.low = Min(u.low, ni.st)

        //For dfs root node, we can't mark it as articulation point because
        //disconnecting it may not decompose graph. So we have extra check just for root node.
        if (u.low = u.st and dfsChild > 0 and parent[u] != null) or (parent[u] = null and dfsChild > 1)
            Output u as articulation point
            Output edges of u with v.low >= u.low as bridges
        output u.low as bicomponent ID


    如果u后代的low大于u后代的dfsnum时,则称u为连接点。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    int adjMatrix[256][256];
    int low[256], num=0, dfsnum[256];

    void cutvertex(int u){
        low[u]=dfsnum[u]=num++;
        for (int v = 0; v < 256; ++v)
        {
            if(adjMatrix[u][v] && dfsnum[v]==-1)
            {
                cutvertex(v);
                if(low[v]>dfsnum[u])    
                    cout<<"Cut Vertex:"<<u<<"
    ";
                low[u]=min(low[u], low[v]);
            }
            else{
                low[u]=min(low[u], dfsnum[v]);
            }
        }
    }