首页 > 其他分享 >Tarjan 求强连通分量

Tarjan 求强连通分量

时间:2023-08-28 13:44:09浏览次数:47  
标签:Tarjan dfs 返祖 text 连通 SCC dfn low 求强

欢迎批评指正!

前置芝士

  • 什么是强连通分量(\(\text{SCC}\))?
    强连通分量,一般指 有向图的极大强连通子图,在这些子图中,所有点双向可达
  • dfs 序:即 dfs 过程中访问点的顺序。
  • dfs 生成树:由 dfs 过程中访问的边组成的边集原图的点集 组成的树。
  • 树边,非树边:属于 dfs 过程中访问的边 为树边,否则为非树边。image
  • 返祖边(反向边):从孩子连接到祖先的边。
  • 前向边:返祖边:从祖先连接到孩子的边。
  • 横插边:除返祖边和前向边之外的非树边。(连接没有祖孙关系的两点的边。)

image

  • 某个强连通分量的根:这个强连通分量重 dfs 序最小的节点。

Tarjan 算法

设两个数组 \(dfn\) 和 \(low\),分别表示 dfs 序和仅通过 \(1\) 条非树边所能到达的点的最小 \(dfn\)
看下面这张图:

image
每个节点的编号就是它的 \(dfn\),旁边标注的数字是 \(low\),可以自行理解一下。

另外,我们还需要一个栈 \(stk\) 存节点。

算法流程

Tarjan 是在 dfs 中实现的,每次访问到当前节点 \(u\),就将它压入 \(stk\) 中。随后访问所有相邻的点 \(v\)。

  • 如果没访问过,说明 \((u,v)\) 是一条树边,继续 dfs。
    dfs \(v\) 的子树结束后,更新 \(low_u=\min(low_u,low_v)\)。
    因为根据 \(low\) 的定义,走多少条树边都没关系,而 \((u,v)\) 是一条树边,我们可以从 \(u\) 走到 \(v\) 后再继续往上爬。也就是说,\(v\) 能到的 \(low_v\),\(u\) 也能到。于是更新。
  • 如果 \(v\) 已被访问过且已属于另一个 \(\text{SCC}\),说明 \((u,v)\) 是一条横插边,两个 \(\text{SCC}\)无关,直接跳过,不予处理。
    证明:因为如果当前 \(\text{SCC}\) 能访问到另一个 \(\text{SCC}\) 且我们希望两个 \(\text{SCC}\) 合并(有关),那另外那个 \(\text{SCC}\) 也应该能访问到当前 \(\text{SCC}\),那就是说两个 \(\text{SCC}\) 是同一个,与我们的假设:“访问另一个 \(\text{SCC}\)”相悖,得证。(可能会很绕,可以先不理解。)
  • 如果 \(v\) 被访问过且在 \(stk\) 内,说明 \((u,v)\) 是一条返祖边,更新 \(low_u=\min(low_u,dfn_v)\)。
    因为 \(low\) 的定义为仅通过 \(1\) 条非树边,故可以直接走返祖边 \((u,v)\),如果 \(dfn_v\) 比 \(low_u\) 小,则更新。
    关于为什么不是 \(low_u=\min(low_u,low_v)\),因为 \(low_v\) 可能已经经过了一条非树边,如果再走返祖边 \((u,v)\),就可能走了两条非树边,与 \(low\) 的定义相悖。(其实这样些也可以得到正确答案,但是求割点时就会错。)

然后判断 \(u\) 是否是根:若 \(dfn_u=low_u\),则是当前 \(\text{SCC}\) 的根,然后退栈到 \(u\),保存答案。
证明:
因为 \(dfn_u=low_u\),所以 \(u\) 的子树内的所有点都不能通过返祖边到达 \(dfn\) 比 \(u\) 小的点(否则可以通过树边到达这些子节点,然后走返祖边。使 \(low_u<dfn_u\)。)
必要性:若反之 \(dfn_u>low_u\)(\(dfn_u<low_u\) 是不可能的),\(u\) 必然可以到达 \(dfn\) 更小的点,那么 \(u\) 就必然不是当前 \(\text{SCC}\) 的根。
充分性:若有 \(dfn_u=low_u\),则 \(u\) 的子树内的点逃不出 \(u\) 的子树,无法到达 \(dfn\) 更小的点,又因为如果有 \(u\) 的子树内的点时某\(\text{SCC}\) 的根,那必然已被退栈,故退栈到 \(u\) 的那些点必然是当前 \(\text{SCC}\) 的点。

代码

代码转载自这篇大佬的博客

stack<int> stk;
// instk:是否在栈中  scc:每个点所属的强连通分量编号  cscc:强连通分量的数量
int dfsn[MAXN], low[MAXN], instk[MAXN], scc[MAXN], cnt, cscc;
void tarjan(int p)
{
    low[p] = dfsn[p] = ++cnt;
    instk[p] = 1;
    stk.push(p); // 进栈
    for (auto q : edges[p])
    {
        if (!dfsn[q]) // 未访问过
        {
            tarjan(q); // 递归地搜索
            low[p] = min(low[p], low[q]);
        }
        else if (instk[q]) // 访问过,且q可达p
            low[p] = min(low[p], dfsn[q]);
    }
    if (low[p] == dfsn[p]) // 发现强连通分量的根
    {
        int top;
        cscc++;
        do
        {
            top = stk.top();
            stk.pop();
            instk[top] = 0;
            scc[top] = cscc; // 记录所属的强连通分量
        } while (top != p); // 直到弹出p才停止
    }
}

标签:Tarjan,dfs,返祖,text,连通,SCC,dfn,low,求强
From: https://www.cnblogs.com/chargedcreeper/p/tarjan-scc.html

相关文章

  • hdu:Oil Deposits(dfs连通图)
    ProblemDescriptionTheGeoSurvCompgeologicsurveycompanyisresponsiblefordetectingundergroundoildeposits.GeoSurvCompworkswithonelargerectangularregionoflandatatime,andcreatesagridthatdividesthelandintonumeroussquareplots.......
  • tarjan
    割点与桥简介割点:对于一个无向图,如果把一个点及与其相连的边删除后这个图分裂为两个及两个以上不连通的子图,那么这个点就是这个图的割点(又称割顶)。割边:对于一个无向图,如果把一条边删除后这个图分裂为两个不连通的子图,那么这个点就是这个图的割边(又称桥)。tarjan求割点定理:如......
  • Tarjan基础用法
    \(\operatorname{Tarjan}\)基础用法目录\(\operatorname{Tarjan}\)基础用法\(\operatorname{Tarjan}\)求最近公共祖先前置芝士实现过程例题\(\operatorname{Tarjan}\)求割点、割边前置芝士\(\operatorname{Tarjan}\)求割点\(\operatorname{Tarjan}\)求割边例题\(\operatorn......
  • 如何优雅的使用telnet测试端口连通性
    telnet命令是TELNET协议的用户接口,它支持两种模式:命令模式和会话模式,虽然telnet支持许多命令,但大部分情况下,我们只是使用它查看目标主机是否打开了某端口(默认是23)。其执行结果有两种:端口未打开$telnet101.199.97.6562715Trying101.199.97.65...telnet:connecttoaddres......
  • 强连通分量
    目录强连通分量dfs森林强连通分量dfs森林树边(treeedge)返祖边(backedge)......
  • 连通分量专题
    图上问题->树上问题->序列问题连通分量专题强连通分量(SCC)对于一个有向图,当其中任意两点都能互相到达时,我们认为这是强联通的intdfn[N],low[N],belong[N],cnt,tot;boolinstack[N];vector<int>scc[N];stack<int>st;voiddfs(intu){ dfn[u]=low[u]=++cnt; st.push(u);......
  • [Tarjan] 学习笔记
    原理强连通分量讲得超级屌,这次比董晓好得多voidtarjan(intx){ dfn[x]=low[x]=t++; s.push(x); in[x]=true; for(inti=h[x];i;i=e[i].next) { inty=e[i].to; if(!dfn[y]) { tarjan(y); low[x]=min(low[x],low[y]); } elseif(i......
  • 暑假集训随笔4 强连通分量与点双、边双连通分量
    强连通分量一个在有向图中的概念\(强连通的定义是:有向图G强连通是指,G中任意两个结点连通。\)\(强连通分量(StronglyConnectedComponents,SCC)的定义是:极大的强连通子图\)tarjan算法的一些理解注意到如果一些点属于一个强连通分量,那么从其中一个点一定可以“走到”所有的点,......
  • Tarjan学习笔记
    TarjanTarjan算法是图论中非常常用的算法之一,能解决强连通分量,双连通分量,割点和桥,求最近公共祖先(LCA)等问题。Tarjan算法是基于深度优先搜索的算法,用于求解图的连通性问题。割点如果从图中删除节点\(x\)以及所有与\(x\)关联的边之后,图将被分成两个或两个以上的不相连的......
  • 强连通分量与tarjan算法
    强连通分量强连通:若一张有向图的节点两两之间可以互相抵达,那么这一张图是强连通的。强连通分量:极大的强连通子图。对图深度搜索的时候,每一个节点只访问一次,被访问过的节点与边构成搜索树。有向边按照访问的情况可以分为如下4类:1.树边:访问节点走过的边。2.返祖边:指向祖......