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

时间:2013-04-08 07:04:43

标签: algorithm graph complexity-theory graph-algorithm dfs

我在网上搜索过,无法找到任何DFS算法的解释来查找图形的所有关节顶点。甚至没有维基页面。

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

每个节点都有一个变量,它实际上是在观察后边缘并找到朝向根节点的最近和最上面的节点。在处理完所有边之后,就会找到它。

但我不明白如何找到这个& amp;在执行DFS期间,每个节点都有变量。这个变量到底是做什么的?

请解释算法。

感谢。

4 个答案:

答案 0 :(得分:35)

查找关节顶点是DFS的应用程序。

简而言之,

  1. 在图表上应用DFS。获取DFS树。
  2. 之前访问过的节点是这些节点的“父节点”,它们到达并稍后访问。
  3. 如果节点的任何子节点没有到其父节点的任何祖先的路径,则意味着删除此节点会使该子节点与图形不相交。
  4. 有一个例外:树的根。如果它有一个以上的孩子,那么这是一个发音点,否则不是。
  5. 第3点实质上意味着这个节点是一个关节点。

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

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

答案 1 :(得分:12)

我将尝试深入理解该算法的工作原理,并提供输出Bi-Components和桥梁的注释伪代码。

为关节点开发蛮力算法实际上很容易。只需取出一个顶点,然后在图形上运行BFS或DFS。如果它保持连接,那么顶点不是关节点,否则它是。这将在O(V(E+V)) = O(EV)时间内运行。挑战在于如何在线性时间内完成此任务(即O(E+V))。

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

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

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

现在不用多说了,这是伪代码:

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

答案 2 :(得分:2)

似乎所有解释中都忽略了一个事实:

事实1:在深度优先搜索生成树(DFSST)中,每个后缘将一个顶点连接到其祖先之一。

这对于算法的工作至关重要,这就是为什么任意生成树对算法不起作用的原因。这也是为什么当根具有多个子节点时,它是一个关节点的原因:以生成树的根子节点为根的子树之间不能有后缘。

该语句的证明是,令(u,v)为后端,其中u不是v的祖先,并且(WLOG)u在DFS中的v之前被访问。假设p是u和v的最深祖先,则DFS必须先访问p,然后访问u,然后再以某种方式再次访问p,然后再访问v。但是由于存在边沿,因此无法在访问v之前重新访问p在u和v之间。


在DFSST中以c为根的子树中调用顶点集V(c)
调用N(c)一组在V(c)中具有邻居的顶点集

事实2:

对于非根节点u,
如果u的子c等于N(c)⊆V(c)∪{u},则u是一个关节点。

原因:对于V(c)中的每个顶点w,从根到w的每条路径都必须包含u。如果不是这样,由于事实#1,这样的路径将必须包含将u的祖先连接到u的后代的后边缘,从而使N(c)大于V(c)。

事实3:

事实2的反面也是正确的。

原因:u的每个后代都有一个不通过u的根路径。 V(c)的后代可以通过一条将V(c)连接到N(c)/ V(c)的后缘绕过u。


因此对于该算法,您只需要了解关于每个非根顶点u的两件事:

  1. 顶点深度,例如D(u)
  2. N(u)的最小深度,也称为低点,可以说L(u)

因此,如果顶点u的子级为c,且L(c)小于D(u),则意味着以c为根的子树的后缘延伸到u的祖先,从而使其不事实3的发音点。相反,事实2也是如此。

答案 3 :(得分:0)

如果low的后代的u大于dfsnum的{​​{1}},则u被称为关节点。

u