割点(Tarjan算法)

本文可转载,转载请注明出处:www.cnblogs.com/collectionne/p/6847240.html 。本文未完,如果不在博客园(cnblogs)发现此文章,请访问以上链接查看最新文章。

前言:之前翻译过一篇英文的关于割点的文章(英文原文翻译),但是自己还有一些不明白的地方,这里就再次整理了一下。有兴趣可以点我给的两个链接。

割点的概念

无向连通图中,如果将其中一个点以及所有连接该点的边去掉,图就不再连通,那么这个点就叫做割点(cut vertex / articulation point)。

例如,在下图中,0、3是割点,因为将0和3中任意一个去掉之后,图就不再连通。如果去掉0,则图被分成1、2和3、4两个连通分量;如果去掉3,则图被分成0、1、2和4两个连通分量。

怎么求割点

直接DFS

最容易想到的方法就是依次删除每个割点,然后DFS,但这种方法效率太低,这里不做讨论。

DFS树

首先需要了解一些关于DFS树(DFS tree)的概念。以下图为例:

从点1开始搜索整个图, 对于每个点相邻的顶点,按照顶点编号从小到大搜索(也可以按其它顺序)。因此上图的搜索顺序如下:

第1步,与1相邻的点有{2, 4},选2。

第2步,与2相邻的点有{1, 3, 4},1访问过,选3。

第3步,与3相邻的点有{2, 5},2访问过,选5。

第4步,与5相邻的点有{3},访问过,退出。

退回第3步,与3相邻的点有{2, 5},都访问过,退出。

退回第2步,与2相邻的点有{1, 3, 4},1、3访问过,选4。

第5步,与4相邻的点有{1, 2},都访问过,退出。

退回第2步,与2相邻的点有{1, 3, 4},都访问过,退出。

退回第1步,与1相邻的点有{2, 4},都访问过,退出。

至此,访问结束。

把访问顶点的路径表示出来就是这样的(访问已访问过的顶点时加上删除线并不再访问,end表示与某个顶点相邻的顶点遍历完毕,{}里是与一个顶点相邻的所有顶点)。

1 {2,4}
  2 {1,3,4}
    1
    3 {2,5}
      2
      5 {3}
        3
        end
      end
    4 {1,2}
      1
      2
      end
    end
  4
  end

访问路径可以绘制成下图(绿边为访问未访问顶点时经过的边,红边为访问已访问节点是经过的边):

我们把上图称为DFS搜索树(DFS tree),上图中的绿边称为树边(tree edge),红边称为回边(back edge)。通过回边可以从一个点返回到之间访问过的顶点

你可能会有疑问,“访问已访问节点时所经过的边叫回边”,我们上面不是没有访问吗?其实是有的,但是为方便就不写了,而且遇到已访问的边(在后面的算法里)只是简单计算一下,不再继续DFS了。

注意,在上图中,如果与一个顶点相邻A的顶点B是A的父节点,不表示出来,接下来的算法遇到这种情况也不计算

Tarjan算法

可以使用Tarjan算法求割点(注意,还有一个求连通分量的算法也叫Tarjan算法,与此算法类似)。(Tarjan,全名Robert Tarjan,美国计算机科学家。)

首先选定一个根节点,从该根节点开始遍历整个图(使用DFS)。

对于根节点,判断是不是割点很简单——计算其子树数量,如果有2棵即以上的子树,就是割点。因为如果去掉这个点,这两棵子树就不能互相到达。

对于非根节点,判断是不是割点就有些麻烦了。我们维护两个数组dfn[]和low[],dfn[u]表示顶点u第几个被(首次)访问,low[u]表示顶点u及其子树中的点,通过非父子边(回边),能够回溯到的最早的点(dfn最小)的dfn值(但不能通过连接u与其父节点的边)。对于边(u, v),如果low[v]>=dfn[u],即v即其子树能够(通过非父子边)回溯到的最早的点,最早也只能是u,要到u前面就需要u的回边或u的父子边。也就是说这时如果把u去掉,u的回边和父子边都会消失,那么v最早能够回溯到的最早的点,已经到了u后面,无法到达u前面的顶点了,此时u就是割点。

但这里也出现一个问题:怎么计算low[u]。假设当前顶点为u,则默认low[u]=dfn[u],即最早只能回溯到自身。有一条边(u, v),如果v未访问过,继续DFS,DFS完之后,low[u]为low[u]和low[v]中的最小值,即low[u]=min(low[u], low[v]);如果v访问过(且u不是v的父亲),就不需要继续DFS了,一定有dfn[v]<dfn[u],low[u]为low[u]和dfn[v]中的最小值,即low[u]=min(low[u], dfn[v])。(哎,好难啊~)

代码

DFS

先回忆一下怎么用DFS遍历一个图,代码如下:

bool vis[N];

// 调用dfs()前需将整个vis[]设为false
void dfs(int u)
{
    vis[u] = true;
    for (int v: edgesOf(u))
    {
        if (!vis[v])
            dfs(v);
    }
}

Tarjan算法

首先假设u是根节点。如果u有两棵以上的子树,则u为割点。代码:

int children = 0;
for (int v: edgesOf(u))
{
    if (!vis[v])
    {
        children++;
        dfs(v);   // 继续DFS
    }
}
if (children >= 2)
    // u是割点

首先,“根节点有n棵子树”这句话,是说这n棵子树是独立的,没有根节点不能互相到达。因此n不一定等于与根节点相邻的顶点数。因此加入了vis[v]为false的条件,因为如果(u, v1)和(u, v2)在一棵子树里,对v1进行DFS,一定能去到v2,vis[v2]就会为true,此时就不会children++了。

非根节点呢?按照前面的描述,代码如下:

// 默认u不能回溯到任何前面的点
low[u] = dfn[u];
for (int v: edgesOf(u))
{
    // (u, v)为树边
    if (!vis[v])
    {
        // 设置v的父亲为u
        parent[v] = u;
        // 继续DFS,遍历u的子树
        dfs(v);
        // u子树遍历完毕,low[v]已求出,low[u]取最小值
        low[u] = min(low[u], low[v]);

        if (low[v] >= dfn[u])
            // u是割点
    }
    // (u, v)为回边,且v不是u的父亲
    else if (v != parent[u])
        low[u] = min(low[u], dfn[v]);
}

综合起来,加上一些其它部分,Tarjan算法的代码如下:

const int V = 50;    // 可以将V改成其它值
const int E = 50;    // 可以将E改成其它值
const int NIL = -1;  // 根节点的parent值

vector<int> e[E];    // e[u]为与u相邻的所有顶点
int dfn[V], low[V], parent[V];
bool vis[V];

// 在其它函数中调用tarjan()前需将parent[u]设为NIL
void tarjan(int u)
{
    static int count = 0;
    int children = 0;

    count++;
    dfn[u] = low[u] = count;
    vis[u] = true;

    for (int v: e[u])
    {
        if (!vis[v])
        {
            children++;
            parent[v] = u;
            tarjan(v);
            low[u] = min(low[u], low[v]);
            if (parent[u] != NIL && low[u] >= dfn[v])
                cout << "AP: " << u << endl;
        }
        else if (v != parent[u])
            low[u] = min(low[u], dfn[v]);
    }
    if (parent[u] == NIL && children >= 2)
        cout << "AP: " << u << endl;
}

Todo

对算法的详细理解

时间: 2024-10-17 18:44:53

割点(Tarjan算法)的相关文章

割点 —— Tarjan 算法

由于对于这一块掌握的十分不好,所以在昨天做题的过程中一直困扰着我,好不容易搞懂了,写个小总结吧 qwq~ 割点 概念 在无向连通图中,如果将其中一个点以及所有连接该点的边去掉,图就不再连通,那么这个点就叫做割点 . 比如我们现在有一个图: 如果我们将 4 号节点及它的所有边全部删去,那么这个图就变得不再联通,所以 4 号点是一个割点: 同理,5 号节点也是一个割点: 怎么求割点 我们可以用 Tarjan 算法去求割点: 有两个关键的数组: dfn [ i ] :表示编号为 i 的点在 dfs 过

tarjan算法--求无向图的割点和桥

一.基本概念 1.桥:是存在于无向图中的这样的一条边,如果去掉这一条边,那么整张无向图会分为两部分,这样的一条边称为桥无向连通图中,如果删除某边后,图变成不连通,则称该边为桥. 2.割点:无向连通图中,如果删除某点后,图变成不连通,则称该点为割点. 二:tarjan算法在求桥和割点中的应用 1.割点:1)当前节点为树根的时候,条件是“要有多余一棵子树”(如果这有一颗子树,去掉这个点也没有影响,如果有两颗子树,去掉这点,两颗子树就不连通了.) 2)当前节点U不是树根的时候,条件是“low[v]>=

Tarjan算法:求解图的割点与桥(割边)

简介: 割边和割点的定义仅限于无向图中.我们可以通过定义以蛮力方式求解出无向图的所有割点和割边,但这样的求解方式效率低.Tarjan提出了一种快速求解的方式,通过一次DFS就求解出图中所有的割点和割边. 欢迎探讨,如有错误敬请指正 如需转载,请注明出处 http://www.cnblogs.com/nullzx/ 1. 割点与桥(割边)的定义 在无向图中才有割边和割点的定义 割点:无向连通图中,去掉一个顶点及和它相邻的所有边,图中的连通分量数增加,则该顶点称为割点. 桥(割边):无向联通图中,去

『Tarjan算法 无向图的割点与割边』

无向图的割点与割边 定义:给定无相连通图\(G=(V,E)\) 若对于\(x \in V\),从图中删去节点\(x\)以及所有与\(x\)关联的边后,\(G\)分裂为两个或以上不连通的子图,则称\(x\)为\(G\)的割点. 若对于\(e \in E\),从图中删去边\(e\)之后,\(G\)分裂为两个不连通的子图,则称\(e\)为\(G\)的割边. 对于很多图上问题来说,这两个概念是很重要的.我们将探究如何求解无向图的割点与割边. 预备知识 时间戳 图在深度优先遍历的过程中,按照每一个节点第一

tarjan算法(强连通分量 + 强连通分量缩点 + 桥 + 割点 + LCA)

这篇文章是从网络上总结各方经验 以及 自己找的一些例题的算法模板,主要是用于自己的日后的模板总结以后防失忆常看看的, 写的也是自己能看懂即可. tarjan算法的功能很强大, 可以用来求解强连通分量,缩点,桥,割点,LCA等,日后写到相应的模板题我就会放上来. 1.强连通分量(分量中是任意两点间都可以互相到达) 按照深度优先遍历的方式遍历这张图. 遍历当前节点所出的所有边.在遍历过程中: ( 1 ) 如果当前边的终点还没有访问过,访问. 回溯回来之后比较当前节点的low值和终点的low值.将较小

Tarjan算法与割点割边

目录 Tarjan算法与无向图的连通性 1:基础概念 2:Tarjan判断割点 3:Tarjan判断割边 Tarjan算法与无向图的连通性 1:基础概念 在说Tarjan算法求解无向图的连通性之前,先来说几个概念: <1. 时间戳:在图的深度优先遍历中,按照每一个结点第一次被访问到的时间顺序,依次给予N个结点1~N的整数边集,该标记就被计位"时间戳",计做 \(dfn[x]\). <2. 搜索树:任选一个结点深度优先遍历,每个点只访问一次.产生递归的边构成的树为搜索树. &

【转】BYV--有向图强连通分量的Tarjan算法

转自beyond the void 的博客: https://www.byvoid.com/zhs/blog/scc-tarjan 注:红色为标注部分 [有向图强连通分量] 在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强连通(strongly connected).如果有向图G的每两个顶点都强连通,称G是一个强连通图.非强连通图有向图的极大强连通子图,称为强连通分量(strongly connected components). 下图中,子图{1,2,3,4}为一个强连通分量,因为顶

POJ1523(求连用分量数目,tarjan算法原理理解)

SPF Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 7406   Accepted: 3363 Description Consider the two networks shown below. Assuming that data moves around these networks only between directly connected nodes on a peer-to-peer basis, a

TarJan 算法求解有向连通图强连通分量

[有向图强连通分量] 在有向图G中,如果两个 顶点间至少存在一条路径,称两个顶点强连通(strongly connected).如果有向图G的每两个顶点都强连通,称G是一个强连通图.非强连通图有向图的极大强连通子图,称为强连通分量(strongly connected components). 下图中,子图{1,2,3,4}为一个强连通分量,因为顶点1,2,3,4两两可达.{5},{6}也分别是两个强连通分量. 大体来说有3中算法Kosaraju,Trajan,Gabow这三种!后续文章中将相继