浅谈强连通分量(Tarjan)
强连通分量\(\rm (Tarjan)\)
——作者:BiuBiu_Miku
\(1.\)一些术语
· 无向图:指的是一张图里面所有的边都是双向的,好比两个人打电话 \(U\) 可以打给 \(V\) 同时 \(V\) 也可以打给 \(U\)( 如图1 就是一个无向图)
· 有向图:指的是一张图里面所有的边都是单向的,好比一条单向的公路只能从 \(U→V\) 而不能从 \(V→U\) ( 如图2 就是一个有向图)
· 连通:指的是在 \(\rm \color{red}{无向图}\) 中,任意节点 \(V\) 可以到达任意节点 \(U\) , 如图1 中点 \(1\) 和点 \(2\) 可以互相到达 ,所以点 \(1\) 和点 \(2\) 是联通的
· 强连通:指在 \(\rm \color{red}{有向图}\) 中,某两个点可以互相到达,比 如图2 中 点 \(1\) 和点 \(2\) 就是可以互相到达对方的,虽然不是直接到达,但是可以到达,就将其称为强连通
· 弱连通:指在 \(\rm \color{red}{有向图}\) 中,某两个点若本身不存在强连通关系,但是通过将其看成 \(\rm \color{red}{无向图}\) 使其连通,则将他们称为弱连通
· 强连通分量:指在 \(\rm \color{red}{有向图}\) 中,一些节点存在强连通关系,如图(2) 中节点 \(1,2,3,4\) , 节点 \(5\) , 节点 \(6\) , 节点 \(7\) 分别为图中的四个强连通分量,强连通分量也可以是单独一个节点


\(2.\)\(\rm Tarjan\)算法的思想简述:
· 我们定义两个变量:
\(dfn[i]\) 表示节点 \(i\) 的时间戳(也就是dfs后序遍历的顺序)
\(low[i]\) 表示节点 \(i\) 可以通过一些节点找到比自己时间戳早的时间戳
比如说节点 \(U\) 的时间戳 \((dfn[U])\) 是 \(1\),节点 \(V\) 的时间戳是 \(3\) ,\(V\) 可以到达 \(U\) 则 \(V\) 的 \(low[V]\) 就是 \(1\)
· 关于此算法的流程:
\(\rm Tarjan\) 算法是一个通过对图进行深度优先搜索并通过同时维护一个栈以及两个相关时间戳 (上面提到的两个变量) 的算法。
第一步:建图
可以用邻接矩阵,链式前向星,或者其他东西
\(\rm \color{red}{PS:一定是单向边}\)
第二步:跑图
用 \(dfs\) 从一个节点开始遍历整张图,与此同时更新时间戳。
在 \(dfs\) 过程中每遍历到一个元素,就将其存到栈中,其主要维护的是上文提到的 \(low\) , 因为 \(dfn\) 是 \(dfs\) 的搜索顺序的时间戳,所以从有值之后基本上就不用变化了,而 \(low\) 不能被马上确定,因为在 \(dfs\) 的遍历中,也许当前节点可以到达比当前的 \(low\) 更前的节点,此时我们就要更新他的 \(low\) 变为更前的节点的遍历时间,也就是 \(dfn\) 。
第三步:存强连通分量
当我们搜索完之后,发现某个节点的 \(dfn\) 和 \(low\) 相等时,说明我们找到了一个强连通分量,因为当前节点不能再到达比自己更小的节点了,那么此时,以这个节点为 \(low\) 值得节点自然不会再次被更新了,因为他是按 \(dfs\) 以后序遍历,顺序搜索过来的,因此我们此时就可以开始存强连通分量了,其手段是利用栈将栈首元素进行存储,之后弹出,直到栈首元素为当前点的值为止
我们可以以染色的手段来存储强连通分量,每当找到一个强连通分量,就可以将其的每一个值作为下标,在数组 \(color\) 中进行染色,其存储的值一般为找到的强连通分量的编号,如:假设我找到了一个强连通分量为 \(7,8,9\) ,其又是第二个被找到的强连通分量,则将 \(color[7] color[8] color[9]\) 标为 \(2\)。
\(3.\)\(\rm Tarjan\)算法的代码实现:
题目:在一个有向图中,有n个节点,m条边,现给出这m条边,请输出图中所有的强连通分量。
\(\rm Code:\)
#include<bits/stdc++.h>
using namespace std;
int n,m;
struct edge{ //定义存边的变量
int from,to,next;
} e[10005];
int head[10005];
int cnt;
void Insert(int x,int y){ //链式前向星存边
e[++cnt].from=x;
e[cnt].to=y;
e[cnt].next=head[x];
head[x]=cnt;
}
int dfn[10005]; //上文提到的dfn
int low[10005]; //上文提到的low
int t; //当前搜索的时间,用于给时间戳dfn与low赋初始值
stack<int> s;
int p[100005]; //判断某个元素在不在栈里面
// int tot;
// int color[100005];
void Tarjan(int now){
s.push(now); //讲当前元素放入栈
dfn[now]=low[now]=++t; //讲当前搜索的时间,也就是当前搜过了几个点的数量赋值给时间戳dfn,同时对low进行初始化
p[s.top()]=true;
for(int i=head[now];i;i=e[i].next){ //链式前向星遍历所有节点
int get=e[i].to;
if(!dfn[get]){ //判断当前节点有没有被搜索过
Tarjan(get); //如果没有,那就搜这个节点
low[now]=min(low[now],low[get]); //更新当前节点的low,为什么不是 low[now]=min(low[now],dfn[get]); 呢?我们不妨观察一下,low的值是不是永远≤dfn的?此时既然now可以到达get,那么now自然也可以到达get节点能到达的节点
}
else if(p[get]) low[now]=min(low[now],dfn[get]); //否则判断当前节点在不在栈里,如果不在,就不用理他,如果在那么就可以更新一下当前节点,因为当前节点可以到达get,但此时的low不一定是最终得到的值,所以不能写low[now]=min(low[now],low[get]);
}
if(dfn[now]==low[now]){ //如果两个相等,说明当前节点不能再更新了,不能再找到比自己low更小的值
// tot++;
while(s.top()!=now){ //将栈首到当前元素的所有值弹出队列,说明这堆东西就是一个强连通分量
printf("%d ",s.top());
// color[s.top()]=tot;
p[s.top()]=false; //标记其不在栈里
s.pop();
}
printf("%d",s.top()); //因为只是弹到当前节点,当前节点也是包含在这个强连通分量内的,所以再做一次
// color[s.top()]=tot;
p[s.top()]=false;
s.pop();
printf("\n");
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
int x,y;
scanf("%d%d",&x,&y);
Insert(x,y); //存边
}
for(int i=1;i<=n;i++) //因为图不一定保证是连通的,有可能某个节点不被其他任何节点到达,所以要用for判断一遍如果没有被搜过就搜,搜过了就没他什么事了
if(!dfn[i])
Tarjan(i);
return 0;
}
\(4.\)强连通分量的应用:
· 缩点
因为强连通分量一般为一个环,所以在一些题目中,我们可以把这些环变成一个点来简便搜索,然后把缩小后的点再次连接,建一张新的图,然后开始一系列操作。
参考例题:洛谷 P3387【模板】缩点
\(\rm Code:\)
#include<bits/stdc++.h>
#define MAXN 100005
using namespace std;
long long n,m;
struct edge{
long long from,to,next;
} e[MAXN];
long long head[MAXN];
long long cnt;
long long qlt;
void Insert(long long x,long long y){
e[++cnt].from=x;
e[cnt].to=y;
e[cnt].next=head[x];
head[x]=cnt;
}
long long dfn[MAXN];
long long low[MAXN];
long long t;
stack<int> s;
long long p[MAXN];
long long color[MAXN];
long long f[MAXN];
long long u[MAXN],v[MAXN],l[MAXN];
long long dis[MAXN];
long long mmax;
const long long oo=0x7f7f7f;
void Tarjan(long long now){
s.push(now);
dfn[now]=low[now]=++t;
p[now]=false;
for(long long i=head[now];i;i=e[i].next){
long long get=e[i].to;
if(!dfn[get]){
Tarjan(get);
low[now]=min(low[now],low[get]);
}
else if(!p[get]) low[now]=min(low[now],dfn[get]);
}
if(dfn[now]==low[now]){
qlt++;
while(s.top()!=now){
color[s.top()]=qlt;
p[s.top()]=true;
f[qlt]+=l[s.top()]; //存储缩点后单点的点权
s.pop();
}
color[s.top()]=qlt;
p[s.top()]=true;
f[qlt]+=l[s.top()]; //同上,再做一次
s.pop();
}
}
void dfs(long long now) { //做一遍记忆化搜索来更新从某一个节点的答案(这里也可以用最短路算法来实现)
dis[now]=f[now]; //初始化,自己的点权就是自己
long long mmmax=0;
for(long long i=head[now];i;i=e[i].next){ //遍历每个节点
long long get=e[i].to;
if(!dis[get])dfs(get); //如果节点没被搜过就搜
mmmax=max(mmmax,dis[get]); //更新最大值
}
dis[now]+=mmmax; //更新当前值
}
int main(){
scanf("%lld%lld",&n,&m);
for(long long i=1;i<=n;i++) scanf("%lld",&l[i]);
for(long long i=1;i<=m;i++){
scanf("%lld%lld",&u[i],&v[i]);
Insert(u[i],v[i]);
}
for(long long i=1;i<=n;i++)
if(!dfn[i])
Tarjan(i);
memset(e,0,sizeof(e)); //清零重新建图
memset(head,0,sizeof(head));
cnt=0;
for(long long i=1;i<=m;i++)
if(color[u[i]]!=color[v[i]]) //建立缩点后的图,如果两点不在同一个强连通分量里,说明两个集合不连通,所以将其连通
Insert(color[u[i]],color[v[i]]);
for(long long i=1;i<=qlt;i++)
if(!dis[i]){
dfs(i); //如果当前节点没被搜过,就进行记忆化搜索
mmax=max(dis[i],mmax); //更新最大值
}
printf("%lld\n",mmax); //输出答案
return 0;
}
感谢您的阅读,如大佬有什么建议或本文有什么错误欢迎指出,感谢大佬%%%
浅谈强连通分量(Tarjan)的更多相关文章
- 强连通分量(tarjan求强连通分量)
双DFS方法就是正dfs扫一遍,然后将边反向dfs扫一遍.<挑战程序设计>上有说明. 双dfs代码: #include <iostream> #include <cstd ...
- 有向图强连通分量 Tarjan算法
[有向图强连通分量] 在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强连通(strongly connected).如果有向图G的每两个顶点都强连通,称G是一个强连通图.非强连通图有向图的极 ...
- POJ2186 Popular Cows 强连通分量tarjan
做这题主要是为了学习一下tarjan的强连通分量,因为包括桥,双连通分量,强连通分量很多的求法其实都可以源于tarjan的这种方法,通过一个low,pre数组求出来. 题意:给你许多的A->B ...
- [有向图的强连通分量][Tarjan算法]
https://www.byvoid.com/blog/scc-tarjan 主要思想 Tarjan算法是基于对图深度优先搜索的算法,每个强连通分量为搜索树中的一棵子树.搜索时,把当前搜索树中未处理的 ...
- 图的连通性:有向图强连通分量-Tarjan算法
参考资料:http://blog.csdn.net/lezg_bkbj/article/details/11538359 上面的资料,把强连通讲的很好很清楚,值得学习. 在一个有向图G中,若两顶点间至 ...
- 强连通分量tarjan缩点——POJ2186 Popular Cows
这里的Tarjan是基于DFS,用于求有向图的强联通分量. 运用了一个点dfn时间戳和low的关系巧妙地判断出一个强联通分量,从而实现一次DFS即可求出所有的强联通分量. §有向图中, u可达v不一定 ...
- 图之强连通、强连通图、强连通分量 Tarjan算法
原文地址:https://blog.csdn.net/qq_16234613/article/details/77431043 一.解释 在有向图G中,如果两个顶点间至少存在一条互相可达路径,称两个顶 ...
- 图论-强连通分量-Tarjan算法
有关概念: 如果图中两个结点可以相互通达,则称两个结点强连通. 如果有向图G的每两个结点都强连通,称G是一个强连通图. 有向图的极大强连通子图(没有被其他强连通子图包含),称为强连通分量.(这个定义在 ...
- 求图的强连通分量--tarjan算法
一:tarjan算法详解 ◦思想: ◦ ◦做一遍DFS,用dfn[i]表示编号为i的节点在DFS过程中的访问序号(也可以叫做开始时间)用low[i]表示i节点DFS过程中i的下方节点所能到达的开始时间 ...
随机推荐
- 从执行上下文角度重新理解.NET(Core)的多线程编程[1]:基于调用链的”参数”传递
线程是操作系统能够进行运算调度的最小单位,操作系统线程进一步被封装成托管的Thread对象,手工创建并管理Thread对象已经成为了所能做到的对线程最细粒度的控制了.后来我们有了ThreadPool, ...
- 使用ABBYY FineReader 14查看和编辑PDF
使用ABBYY FineReader,您可以轻松查看和编辑任何类型的 PDF,以及在其中添加注释和进行搜索,即使这些 PDF 是从扫描纸质文档生成.因而不包含任何可疑搜索或编辑的文本.是一款名副其实的 ...
- 什么是NTFS文件格式
说到磁盘格式,想必大家对于NTFS格式并不陌生.我们使用的u盘等硬盘设备很多都应用了此格式.NTFS文件格式究竟是什么?它都有哪些特点?今天,小编将利用这篇文章为大家进行介绍. 一.什么是NTFS文件 ...
- jQuery 第四章 实例方法 DOM操作之data方法
jquery 里面 的 data 方法比较重要, 所以成一个模块写: 首先, 得知道 data() 干嘛用的, 看淘宝上 有自定义的属性, 为data - 什么什么, 这是为了dom 跟数据有 ...
- CLH lock queue的原理解释及Java实现
目录 背景 原理解释 Java代码实现 定义QNode 定义Lock接口 定义CLHLock 使用场景 运行代码 代码输出 代码解释 CLHLock的加锁.释放锁过程 第一个使用CLHLock的线程自 ...
- 慢SQL优化:where id in (select max(id)...) 改为join后性能提升400倍
背景 有两张表,都是主键递增,类似于主表和明细表: statistics_apply:统计申请表,主键applyId,7万多条记录 statistics_apply_progress:统计申请进度表( ...
- 电脑装MySQL免安装版配置失败提示系统错误2怎么解决?
一·准备工作 我下载安装的版本是:mysql-8.0.16-winx64(免安装版) 下载地址:https://www.mysql.com/ (官网地址)https://cdn2.lmonkey.co ...
- 测试中:ANR是什么
1.ANR 的定义 ANR(Application Not Responding),用户可以选择"等待"而让程序继续运行,也可以选择"强制关闭".所以一个流畅的 ...
- sqli-labs-master less01
注:如未接触过sql注入,建议观看前期知识点文章 https://www.cnblogs.com/yyd-sun/p/12256407.html 第一关步骤 一.判断注入类型(数字/字符) (1).h ...
- Docker 跨平台在 netCore 中的从入门到部署
前言 从题目我们可以看的出,今天是五部曲的第三部,你可能会好奇,为啥没有见到前两部呢?这里我简单说下: 1.跨平台第一部曲:MySql 如果你看我的所有开源项目,应该能发现我已经全部迁移到了Mysql ...