浅谈强连通分量(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的下方节点所能到达的开始时间 ...
随机推荐
- 使用Camtasia制作冰雪奇缘视频
冰雪奇缘的精良制作,以及场景的华丽,让很多女孩子都很喜欢.对于其中美丽的冰雪场景,我们还可以使用Camtasia(Windows系统)教程录制软件来做冰雪奇缘视频. Camtasia教程录制软件是一款 ...
- FL Studio进行侧链的三种方式(下)
在上篇教程中我们了解了在FL Studio中进行侧链的第一种方式,今天我们就来继续带领大家了解进行侧链的另外两种方式. 如何使用 Fruity Peak Controller(果味峰值控制器)在FL ...
- leetcode151. 翻转字符串里的单词
给定一个字符串,逐个翻转字符串中的每个单词. 示例 1:输入: "the sky is blue"输出: "blue is sky the"示例 2:输入: & ...
- iOS 默认Cell选中
NSInteger selectIndex = [NSIndexPath indexPathForItem:0 inSection:0]; [self.ui_tableView selectRowAt ...
- H5 ,Css实现了你的logo
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...
- 【模板】【P3605】【USACO17JAN】Promotion Counting 晋升者计数——动态开点和线段树合并(树状数组/主席树)
(题面来自Luogu) 题目描述 奶牛们又一次试图创建一家创业公司,还是没有从过去的经验中吸取教训--牛是可怕的管理者! 为了方便,把奶牛从 1⋯N(1≤N≤100,000) 编号,把公司组织成一棵树 ...
- C语言新手入门
include<stdio.h> int main() { //输入一个数 输出它的反序列 c int a,b=0; scanf("%d",&a);//输入一个 ...
- 通过weakHashMap避免过期引用导致的内存泄漏
问题由来 数组为基础实现的集合在退出元素时,并不会将引用指向空指针,过期引用存在对象便不会被回收. 措施 1.WeakHashMap当其中的key没有再被外部引用时,就会被回收.ThreadLocal ...
- 【mq读书笔记】mq消息发送
钩子的注册: DefaultMQProducerImpl#registerSendMessageHook注册钩子处理类,可注册多个. public SendResult sendMessage( fi ...
- Windows10下的MediaWiki的部署启动
MediaWiki是使用PHP开发的,PHP是比较成熟的Web脚本语言,要想运行基于PHP的程序,你需要配置PHP的运行环境.MediaWiki使用数据库来保存数据等信息,支持MySQL和Postgr ...