参考资料:

图论相关概念 - OI WIKI | 强连通分量 - OI WIKI

初探tarjan算法 | Tarjan,你真的了解吗

一、概念

• 子图:

对一张图 \(G=(V,E)\),若存在另一张图 \(H=(V',E')\) 满足 \(V'\subseteq V\) 且 \(E'\subseteq E\),则称 \(H\) 是 \(G\) 的 子图 (subgraph),记作 \(H \subseteq G\)。——OI WIKI

通俗点说就是如果你能在图 G 中找到一部分和 H 一样,那么 H 就是 G 的子图。

或者这样理解,图 H 里面的所有边都能在 G 里面找到,那么就是子图了。

• 连通和可达

现在有两个点 x 和 y,如果在图 G 里能找到一条从 x 走到 y 的路径,那么就称之为 x 可达 y。如果图 G 是无向图的话,还可以说 x 与 y 连通。

现有三个定义:

  1. 如果无向图 G 里面任意两点都互相连通,那么图 G 就是连通图

  2. 如果有向图 G 里面任意两点都互相可达,那么图 G 就是强连通的

  3. 如果有向图 G 里面的有向边全部都为无向边后,任意两点都互相连通,那么图 G 就是弱连通的

• (强/弱)连通分量

这是上一条的补充,也是这讲里面几乎是最重要的一个概念。

如果在无向图 G 中可以找到一个最大的连通子图 H,那么 H 就是 G 的连通分量

类似的,我们可以将连通分量的定义推广到强连通分量和弱连通分量上

\(\tiny\text{图 1-1}\)

如上图 G,节点 2 3 4 构成的图不是 G 的强连通分量(不是最大的),但是节点 2 3 4 5 6 构成的图是 G 的强连通分量。当然点 1,7,8,9都分别是一个强连通分量

更通俗一点的,强连通分量可以理解为在有向图中找一个最大的环。

• Tarjan

Tarjan是一种快速求出有向图里面强联通分量的算法。

现在,我们将有向有环图抽象成 DFS 树,那么除了树边还会有其它边,比如下图:



\(\tiny\text{图 1-2}\)

其中黑边为树边,红边和绿边为非树边。

特别的,我们规定红边为横插边,也就是非树边的两端没有父子关系;绿边为返祖边,也就是非树边的两端有父子关系。

但是,非树边的两端是节点 8 和节点 7 是不可能的,如果这样的话,当访问到节点 8 时为继续搜索节点 7 ,与现在这颗 DFS 树不符。

不难发现,绿边可以构成一个环,而红边不行。

而 Tarjan 算法一般用于缩点,也就是将一个强连通分量看做一个点,对结果并不影响。

二、实现

依旧要运用上面的 DFS 搜索树来实现 Tarjan

于是我们有了以下变量:

   dfn[i] 表示 i 的 dfs 序
dn 表示 dfs 序数

当然还有链式前项星的那些变量。

判断两个点 u,v 强连通的条件:\(u\) 可达 \(v\),且 \(v\) 可达 \(u\)。

虽然听起来像废话,但我们可以从这个条件入手思考 Tarjan。

既然 \(u\) 要可达 \(v\) 的话,我们就直接遍历 \(u\) 所有可达的节点。

—— 于是

• 第一步:遍历 u

void tarjan(int x){
dfn[x]=++dn;//更新 dfn 数组
for(int i=head[x];i;i=edge[i].nex){
int nex=edge[i].to;
if(dfn[nex]==0){//如果之前没有被访问到
tarjan(nex);//访问
}
}
//当然这里会有一个判断 u 是否可构成强连通分量的语句,但现在不急
}

然后考虑什么时候 \(v\) 可达 \(u\)。

不难发现,当 \(v\) 有一条指向 \(u\) 或者 \(u\) 的祖先的时候,\(v\) 一定可达 \(u\)。

并且,一个节点只能在一个强连通分量里面,不妨用反证法证明一下,如果一个节点同时在两个强连通分量里面,那么这两个互相强连通的分量合在一起也是强连通的,并且比之前的分量大,与之前假设的不相符。

Q:怎么判断 \(u\) 节点是该强联通分量最先访问的节点呢?

再开一个数组存贮 \(low_{[u]}\) 表示 \(u\) 节点及 \(u\) 的子节点中的 \(dfn\) 最小值。

这样当 \(low_{[u]}=dfn_{[u]}\) 时就表示点 \(u\) 是最先访问的节点了。

——所以

  • 第二步:更新 \(low\) 数组

void tarjan(int x){
low[x]=dfn[x]=++dn;//目前dfs序的最小值就是x的dfs序
stk[++top]=x;//入栈
inst[x]=true;
for(int i=head[x];i;i=edge[i].nex){
int nex=edge[i].to;
if(dfn[nex]==0){//如果子节点还没被访问过/还没入过栈
tarjan(nex);//先将它的子节点遍历,更新nex的low值
low[x]=min(low[x],low[nex]);//nex可能会连到x的祖先,所以进行更新的是low值
}
else if(inst[nex]==true){//之前就访问过了,说明它的dfs序比x要小
low[x]=min(low[x],dfn[nex]);//所以进行更新的是dfn值
}
/*
if(inst[nex]==false){
那么nex一定不属于u的强联通分量,直接continue
}
*/
}
}

Q:怎么确保 \(v\) 贡献给 \(u\) 的节点一定可以和 \(u\) 构成强连通分量呢?

考虑维护一个栈,记录 \(u\) 之后可以和它构成强连通分量的点。也就是说,每当遍历到一个点时,先判断一下它有没有已经属于一个强连通分量了,如果已经属于,那么就直接弹出栈。

由于不属于 \(u\) 强联通分量的点都已经被弹出栈了,所以当前在 \(u\) 之后入栈的元素都一定属于它的强联通分量。

——最后一步!

  • 第三步:弹栈

inline void tarjan(int x){
/*前两步代码
low[x]=dfn[x]=++dn;
stk[++top]=x;
inst[x]=true;
for(int i=head[x];i;i=edge[i].nex){
int nex=edge[i].to;
if(dfn[nex]==0){
tarjan(nex);
low[x]=min(low[x],low[nex]);
}
else if(inst[nex]==true){
low[x]=min(low[x],dfn[nex]);
}
}
*/
if(dfn[x]==low[x]){//是当前强联通分量中最先访问的节点
cn++;//染色操作
while(stk[top]!=x){//在其后的都要弹出
int now=stk[top--];
inst[now]=false;
col[now]=cn;
}
int now=stk[top--];//自己也要弹出
inst[now]=false;
col[now]=cn;
}
}

三、时间复杂度

\(O(n)\),结束。

【算法】Tarjan的更多相关文章

  1. 有向图的强连通分量的求解算法Tarjan

    Tarjan算法 Tarjan算法是基于dfs算法,每一个强连通分量为搜索树中的一颗子树.搜索时,把当前搜索树中的未处理的结点加入一个栈中,回溯时可以判断栈顶到栈中的结点是不是在同一个强连通分量中.当 ...

  2. 图论算法-Tarjan模板 【缩点;割顶;双连通分量】

    图论算法-Tarjan模板 [缩点:割顶:双连通分量] 为小伙伴们总结的Tarjan三大算法 Tarjan缩点(求强连通分量) int n; int low[100010],dfn[100010]; ...

  3. POJ 1470 Closest Common Ancestors (最近公共祖先LCA 的离线算法Tarjan)

    Tarjan算法的详细介绍,请戳: http://www.cnblogs.com/chenxiwenruo/p/3529533.html #include <iostream> #incl ...

  4. 有向图的强连通算法 -- tarjan算法

    (绘图什么真辛苦) 强连通分量: 在有向图 G 中.若两个顶点相互可达,则称两个顶点强连通(strongly connected). 假设有向图G的每两个顶点都强连通,称G是一个强连通图.非强连通图有 ...

  5. 强连通分量算法·$tarjan$初探

    嗯,今天好不容易把鸽了好久的缩点给弄完了--感觉好像--很简单? 算法的目的,其实就是在有向图上,把一个强连通分量缩成一个点--然后我们再对此搞搞事情,\(over\) 哦对,时间复杂度很显然是\(\ ...

  6. POJ 1523 SPF 割点与桥的推断算法-Tarjan

    题目链接: POJ1523 题意: 问一个连通的网络中有多少个关节点,这些关节点分别能把网络分成几部分 题解: Tarjan 算法模板题 顺序遍历整个图,能够得到一棵生成树: 树边:可理解为在DFS过 ...

  7. LCA离线算法Tarjan的模板

    hdu 2586:题意:输入n个点的n-1条边的树,m组询问任意点 a b之间的最短距离 思路:LCA中的Tarjan算法,RMQ还不会.. #include <stdio.h> #inc ...

  8. HDU 2874 LCA离线算法 tarjan算法

    给出N个点,M条边.Q次询问 Q次询问每两点之间的最短距离 典型LCA 问题   Marjan算法解 #include "stdio.h" #include "strin ...

  9. LCA(最近公共祖先)离线算法Tarjan+并查集

    本文来自:http://www.cnblogs.com/Findxiaoxun/p/3428516.html 写得很好,一看就懂了. 在这里就复制了一份. LCA问题: 给出一棵有根树T,对于任意两个 ...

  10. 距离LCA离线算法Tarjan + dfs + 并查集

    距离B - Distance in the Tree 还是普通的LCA但是要求的是两个节点之间的距离,学到了一些 一开始我想用带权并查集进行优化,但是LCA合并的过程晚于离线计算的过程,所以路径长度会 ...

随机推荐

  1. MYSQL的Java操作器——JDBC

    MYSQL的Java操作器--JDBC 在学习了Mysql之后,我们就要把Mysql和我们之前所学习的Java所结合起来 而JDBC就是这样一种工具:帮助我们使用Java语言来操作Mysql数据库 J ...

  2. 一文搞懂│XSS攻击、SQL注入、CSRF攻击、DDOS攻击、DNS劫持

    目录 XSS 攻击 SQL 注入 CSRF 攻击 DDOS 攻击 DNS 劫持 XSS 攻击 全称跨站脚本攻击 Cross Site Scripting 为了与重叠样式表 CSS 进行区分,所以换了另 ...

  3. 【java】学习路径39-Buffered缓冲输出流

    import java.io.BufferedOutputStream; import java.io.FileOutputStream; import java.io.IOException; pu ...

  4. 【java】学习路径28-Java集合类知识点总结+练习题(去重)

    Java集合 1.集合和数组的区别 (1)  集合可以改变长度 (2)  数组长度不可变 2.ArrayList (1)  add addAll (2)  remove removeAll (3)   ...

  5. 「学习笔记」倍增思想与lca

    目录 ST表 算法 预处理 查询 关于 log2 Code 预处理 查询 例题 P2880 P2048 lca 树上 RMQ 前置知识:欧拉序列 算法 Code 离线 Tarjan 算法 Code 倍 ...

  6. c++的一些笔记

    --const 的一些用法 1,修饰指针 const int *p=....   可以改变指针所指的位置,但不能改变指向位置的值. 2,修饰变量 int const * p=....  可以改变指向位 ...

  7. 记一次用arthas排查jvm中CPU占用过高问题

    记一次使用arthas排查jvm中CPU占用过高问题.这工具屌爆了 碾压我目前使用的全部JVM工具. 安装 小试 curl -O https://arthas.aliyun.com/arthas-bo ...

  8. rtmp/rtsp/hls公网测试地址

    相信大家在调试播放器的时候,都有这样的困惑,很难找到合适的公有测试源,以下是大牛直播整理的真正可用的直播地址源. 其中,rtmp和rtsp的url,用https://github.com/daniul ...

  9. 【设计模式】Java设计模式 - 外观模式

    Java设计模式 - 外观模式 不断学习才是王道 继续踏上学习之路,学之分享笔记 总有一天我也能像各位大佬一样 原创作品,更多关注我CSDN: 一个有梦有戏的人 准备将博客园.CSDN一起记录分享自己 ...

  10. vue项目中使用百度富文本编辑器ueditor

    第一步,安装依赖,并且把ueditor整个文件夹放入public里边 第二步,在你需要编辑的地方引入,或者main.js中全局引入 XX.vue文件中写入下面代码,创建编辑器. <vue-ued ...