【算法】Tarjan
参考资料:
图论相关概念 - OI WIKI | 强连通分量 - OI WIKI
一、概念
• 子图:
对一张图 \(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 连通。
现有三个定义:
如果无向图 G 里面任意两点都互相连通,那么图 G 就是连通图。
如果有向图 G 里面任意两点都互相可达,那么图 G 就是强连通的。
如果有向图 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的更多相关文章
- 有向图的强连通分量的求解算法Tarjan
Tarjan算法 Tarjan算法是基于dfs算法,每一个强连通分量为搜索树中的一颗子树.搜索时,把当前搜索树中的未处理的结点加入一个栈中,回溯时可以判断栈顶到栈中的结点是不是在同一个强连通分量中.当 ...
- 图论算法-Tarjan模板 【缩点;割顶;双连通分量】
图论算法-Tarjan模板 [缩点:割顶:双连通分量] 为小伙伴们总结的Tarjan三大算法 Tarjan缩点(求强连通分量) int n; int low[100010],dfn[100010]; ...
- POJ 1470 Closest Common Ancestors (最近公共祖先LCA 的离线算法Tarjan)
Tarjan算法的详细介绍,请戳: http://www.cnblogs.com/chenxiwenruo/p/3529533.html #include <iostream> #incl ...
- 有向图的强连通算法 -- tarjan算法
(绘图什么真辛苦) 强连通分量: 在有向图 G 中.若两个顶点相互可达,则称两个顶点强连通(strongly connected). 假设有向图G的每两个顶点都强连通,称G是一个强连通图.非强连通图有 ...
- 强连通分量算法·$tarjan$初探
嗯,今天好不容易把鸽了好久的缩点给弄完了--感觉好像--很简单? 算法的目的,其实就是在有向图上,把一个强连通分量缩成一个点--然后我们再对此搞搞事情,\(over\) 哦对,时间复杂度很显然是\(\ ...
- POJ 1523 SPF 割点与桥的推断算法-Tarjan
题目链接: POJ1523 题意: 问一个连通的网络中有多少个关节点,这些关节点分别能把网络分成几部分 题解: Tarjan 算法模板题 顺序遍历整个图,能够得到一棵生成树: 树边:可理解为在DFS过 ...
- LCA离线算法Tarjan的模板
hdu 2586:题意:输入n个点的n-1条边的树,m组询问任意点 a b之间的最短距离 思路:LCA中的Tarjan算法,RMQ还不会.. #include <stdio.h> #inc ...
- HDU 2874 LCA离线算法 tarjan算法
给出N个点,M条边.Q次询问 Q次询问每两点之间的最短距离 典型LCA 问题 Marjan算法解 #include "stdio.h" #include "strin ...
- LCA(最近公共祖先)离线算法Tarjan+并查集
本文来自:http://www.cnblogs.com/Findxiaoxun/p/3428516.html 写得很好,一看就懂了. 在这里就复制了一份. LCA问题: 给出一棵有根树T,对于任意两个 ...
- 距离LCA离线算法Tarjan + dfs + 并查集
距离B - Distance in the Tree 还是普通的LCA但是要求的是两个节点之间的距离,学到了一些 一开始我想用带权并查集进行优化,但是LCA合并的过程晚于离线计算的过程,所以路径长度会 ...
随机推荐
- SpringBoot定时任务 - 开箱即用分布式任务框架xxl-job
除了前文介绍的ElasticJob,xxl-job在很多中小公司有着应用(虽然其代码和设计等质量并不太高,License不够开放,有着个人主义色彩,但是其具体开箱使用的便捷性和功能相对完善性,这是中小 ...
- 【AGC】增长服务1-远程配置示例
[AGC]增长服务1-远程配置示例 前言:上一次笔者给大家带来了AGC领域的性能管理服务的学习.这次我们再继续深化学习AGC的相关知识.在文章开始之前,再给读者简单介绍一下AGC,以免第一次来的读 ...
- 6.14 YZBOI模拟赛solution
\(6.14\ YZBOI\)模拟赛\(solution\) 本来不想写题解来着...毕竟是自己找的题还是写一写吧 上午为了整活,就把赛制改成\(IOI\)赛制了,于是乎拯救了大家的\(70pts\) ...
- Linux 09 Vim
参考源 https://www.bilibili.com/video/BV187411y7hF?spm_id_from=333.999.0.0 版本 本文章基于 CentOS 7.6 概述 Vi Vi ...
- 推荐软件(一):Motrix——磁力下载器
个人觉得迅雷这样的下载器广告又多,启动速度又慢,又占用内存和存储,非常地不好用.有时候下载速度也不是你自己网速的最大值,而且有一些资源也会因为版权问题阻止你下载. Motrix 界面非常简洁:下载速度 ...
- 熔断器-Hystrix。。。之降级方法
与Feign的Fallback降级方法不同,这个hystrix降级方法是写在被调用方的 需要依赖: <dependency> <groupId>org.springframew ...
- JWT签发与解析
需要的依赖: <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</ ...
- Live2d Widget
写在最前 最早的时候看别人的博客很多都有一个可爱的看板娘,然后就找了教程给自己也整了一个.因为找到的教程都是稂莠不齐的,原作者自己说的也略显含糊(其实是我自己看不懂).总之秉承着一如既往的小白风格.把 ...
- 从零开始Blazor Server(15)--总结
我们用了14篇文章,基本上把一个后台管理系统需要的UI部分都说的差不多了.所以这套文章也该到了结束的时候了. 这里面有很多问题,比如我们直接使用UI来拉数据库信息而没有使用service,再比如我们大 ...
- PTA C语言作业
6-1 使用函数输出一个整数的逆序数 (20 分) 本题要求实现一个求整数的逆序数的简单函数. 函数接口定义: int reverse( int number ); 其中函数reverse须返回用户 ...