什么是强连通分量

强连通分量,是一个有向图的最大强连通子图(看起来好像没有什么解释效果…),好吧,强连通分量就是在一个有向图中,从任意一个点出发,最多可以走过的所有的点构成的一个点集,将其称之为强连通。强连通分量就是这些集合里面最大的那个。如下图:

2,3,5,就是强连通的,因为从二出发可以到达2,3,5的任意一个点。所以1,4,也是强连通的。我们不难发现,除了双向边以外,强连通都是环套环的形式。今天,我们的算法也会在这张图上展开。

算法详解

其实求强连通的tarjan算法的本质就是求环套环,我们将以邻接表为基础讲讲求强连通的tarjan算法。不懂邻接表的朋友看这里:邻接表存图法,那么这个算法就是要求环套环了嘛。

怎么求环套环呢?本算法中引入了时间戳这个概念,就是在进行dfs遍历(我相信看这个blog的朋友都会dfs把)的时候,遍历到的第一个点就打上1这个时间戳,第二个点就打上2这个时间戳。第n个点就打上n这个时间戳,我们把存每个点的时间戳的数组取名为dfn,则dfn[i]就是第i个点的时间戳。那么在dfs的时候,就可以免去记录此点是否走过的数组,而直接判断,凡是有时间戳的都是已经走过的点。

我们再使用一个数组color来染色,凡是在一个强连通分量里边的就把它染成一个相同的数字。比如2,3,5在一个强连通分量里面,那color[2],color[3],color[5]就把它赋值成一个数字(什么时候染色一会讲)。我们还需要一个数组low,每一个点都有自己的low,记录方法和dfn是一样的,每一个点的low初始是和dfn一样的,当我们dfs的时候,如果形成环路,那就必然会dfs到一个已经dfs过得点,而且这个点的时间戳还比自己小,那么就需要把那个已经dfs过的点的low拿来更新自己的low,然后一路递归地更新回去,这样,在这个环里dfs过的点就会有相同的low值。例如:上图中2 dfs 到3,3到5,至此,点2,3,5的low值都是自己的dfn,但是你会发现点5除了点2搜不到任何点了,那么就拿2的low更新自己的,然后递归回去,也一路更新回去,点3的用点5的更新…

然而这还是不能完成染色的,尽管在一个环里面的low都已经是一样的了。因此,我们创建一个栈。每搜到一个点,就把它加入栈中。什么时候退栈呢?比如我们从2开始搜索的,最后到了点5,然后2,3,5,都已经在栈中了,开始return了,于是点5退到了点2,然后会发现点2的low和dfn是一样的(因为大家其实都是拿点2来更新自己,所以2自己的没动),那就开始退栈。每退出一个元素,就把这个元素染色,直到把自己退掉。至此,染色完成,所有染成一样的点都在一个强连通分量里面。

实例模拟

我们就这个图进行一次模拟。
我们从点1开始深搜,如图,点1被打上时间戳(D)和low(L),并被加入栈中

打时间戳和low并入栈代码如下:

dfn[u]=low[u]=++cnt;
st[++top]=u;

其中u就是当前被深搜的点。
然后我们搜到了点二,如图:

点2也被打上时间戳和low,并且加入栈中,此时栈中有点1,2.

以此法,最后会这样:

此时栈中有:1 2 3 5

然后5搜到了2,但是2已经搜过了,而且2不在别的强连通分量里面,所以需要用2的low更新5的(对于5来说2就是下一个点,而邻接表结构体成员中恰恰会存u,v,也就是5到2,所以我们可以很方便的取到2的low值)。5的更新完毕后,就开始return了,return到3,也要用5的low更新3的。所以更新到2会是这样:

这里我给出深搜的代码和更新的代码(其实这两个本来就是一起写的):

for(int i=head[u];i;i=e[i].next){if(dfn[e[i].v]==0){tarjan(e[i].v);low[u]=min(low[u],low[e[i].v]);}else if(color[e[i].v]==0){low[u]=min(low[u],low[e[i].v]);}
}

其中,用2更新5的时候,其实是执行else if语句里面的更新,因为2走过了,所以dfn[e[2].v]不可能等于0,因此第一个if他根本不会进去的。所以才需要第二的if,else if(color[e[i].v]==0)用于判断其是否已经属于别的强连通分量了,被染色当然就不会等于0了。属于别人的我们不要,我们要单身的~~不要二手——
好了,现在又回到了点2,按我刚刚描述的,要开始退栈染色了兄弟。代码如下:

if(low[u]==dfn[u]){color[u]=++col;while(st[top]!=u){color[st[top]]=col;top--;} top--;
}

这段代码紧跟在循环的下面(不在循环内部),这段代码只有在递归的时候才会触发,到了点2的时候(我们递归回到了点2),然后发现点2的low和时间戳都是一样的。所以就进入这个判断。一进入判断,先把当前的点更新一下,因为这个while是先判断后循环,所以到最后是不会染色自己的(一旦st[top]==u,就直接退出了,不再color[st[top]]=col了)。当然,你也可以尝试写do-while循环,这样就不需要事先给自己染色。col其实就是本次染色要染成的数字。while循环下面还有一个top----,目的是使自己——当前的点也可以退出栈,因为st[top]==u就退出了循环,也就没有top----了,原理和刚刚那个事先染色的差不多。

大家可能会有一个问题,递归回来的路上,都会先经过low[u]=min(low[u],low[e[i].v]);然后被上一个点更新,导致low和dfn不同,然后进入不了if(low[u]==dfn[u])这个判断,凭什么点2就可以呢?因为,我们一开始就是拿点2更新的,所以点2的确是走过了low[u]=min(low[u],low[e[i].v]);这个赋值,然而走过了之后自己还是自己啊,自己就是最小的。所以点2的low和dfn其实都还是原来的,而且还相等,所以…进入判断了。

点2的判断也结束后,就回到了点1,于是点1就搜到了点4,所以点4也被打上时间戳,计数变量cnt不会重置,所以点4的时间戳就是4,low也是4。如图:

然后点4又搜回了点1(这两个是双向的),但是点1已经是搜过的,所以执行else if那一句,把自己的low变成了点1的了,然后再return回去,直到深搜退出。最终会是酱紫:

下面我给出完整的代码和样例输入输出

样例输入 样例输出
5 7 2
1 2 1
1 4 1
1 5 2
2 3 1
3 5
4 1
5 2
1
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxm=1001;
int p,s,a,b;
int dfn[maxm],low[maxm],cnt;//时间戳、最小戳、计数
int st[maxm],top;
int color[maxm],col;//染色、强连通分量个数 struct edge{int u,v,next;
}e[maxm];
int head[maxm],js;void addedge(int u,int v){e[++js].u=u;e[js].v=v;e[js].next=head[u];head[u]=js;return;
}void tarjan(int u){dfn[u]=low[u]=++cnt;st[++top]=u;for(int i=head[u];i;i=e[i].next){if(dfn[e[i].v]==0){tarjan(e[i].v);low[u]=min(low[u],low[e[i].v]);}else if(color[e[i].v]==0){low[u]=min(low[u],low[e[i].v]);}}if(low[u]==dfn[u]){color[u]=++col;while(st[top]!=u){color[st[top]]=col;top--;} top--;}return;
}int main(){cin>>p>>s;for(int i=1;i<=s;i++){cin>>a>>b;addedge(a,b);}int start;cin>>start;tarjan(start);for(int i=1;i<=p;i++) cout<<color[i]<<endl;return 0;
}

手累,大家多模拟模拟就一定可以学会哦…

Tarjan算法——求强连通分量相关推荐

  1. 算法提高课-图论-有向图的强连通分量-AcWing 1174. 受欢迎的牛:tarjan算法求强连通分量、tarjan算法板子、强连通图

    文章目录 题目解答 题目来源 题目解答 来源:acwing 分析: 强连通图:给定一张有向图.若对于图中任意两个结点x,y,既存在从x到y的路径,也存在从y到x的路径,则称该有向图是"强连通 ...

  2. POJ2186——并查集+Tarjan算法求强连通分量

    算法讨论:这题陷阱比较多.首先,被所有牛欢迎,这说明所有的牛都要在一个连通图中,也就是将所给的边看成无向边的时候,所有点要在一个连通图中.这个我们用并查集来实现就可以了.强连通分量的求法就很简单了,正 ...

  3. tarjan算法求解强连通分量问题

    Part1:有向图的强连通分量: 一个连通图只有一个联通分量就是自身,非连通图有多个连通分量. 在有向图G中,如果两个顶点vi,vj间(vi>vj)有一条从vi到vj的有向路径,同时还有一条从v ...

  4. tarjan算法(强连通分量与割点)

    tarjan算法可以求有向图的割点割边强连通分量(还有一些奇奇怪怪的操作) 我只会割点和强连通分量,割边(和缩点)以后可能会加,如果是来看割边的话,现在跑还来得及.. (先来一张有向图叭) 用这张图, ...

  5. tarjan算法总结 (强连通分量+缩点+割点),看这一篇就够了~

    文章目录 一.tarjan求强连通分量 1:算法流程 2:模板 二.tarjan缩点 1:相关定义 2:算法流程 三.tarjan求割点.桥 1.什么是割点 2.割点怎么求? 3.割点tarjan模板 ...

  6. 迷宫城堡 HDU - 1269 (塔尖算法求强连通分量)

    为了训练小希的方向感,Gardon建立了一座大城堡,里面有N个房间(N<=10000)和M条通道(M<=100000),每个通道都是单向的,就是说若称某通道连通了A房间和B房间,只说明可以 ...

  7. 解题报告:luogu P2341 受欢迎的牛(Tarjan算法,强连通分量判定,缩点,模板)

    题目链接:洛谷 受欢迎的牛 基本上算是一道模板题 根据题意,如果有环,意味着这个环里的牛都互相喜欢 我们可以先求出环,然后把每一个环都看作一个点,这样整个图就变成了一个DAG(有向无环图) 看有几个点 ...

  8. Kosaraju算法求强连通分量

    Kosaraju算法 该算法旨在得到深度优先后续排列后的递归探索中,每次调用DFS的所有顶点都属于同一强联通分量.所以可以这么理解:当递归进入一个强联通分量时,把他锁死在这个强联通分量中(即不能从该强 ...

  9. bzoj1051[kosaraju算法]求强连通分量

    之前一直用的是tarjan第一次学习到这个来试一下. 唔,就是裸的算法,然后如果出度为0的点只有一个,输出这个点的大小. #include<iostream> #include<cs ...

  10. Tarjan 算法思想求强连通分量及求割点模板(超详细图解)

    割点定义 在一个无向图中,如果有一个顶点,删除这个顶点及其相关联的边后,图的连通分量增多,就称该点是割点,该点构成的集合就是割点集合.简单来说就是去掉该点后其所在的连通图不再连通,则该点称为割点. 若 ...

最新文章

  1. 我所理解的UTF-8和GBK
  2. boostrap 鼠标滚轮滑动图片_BootStrap 轮播插件(carousel)支持左右手势滑动的方法(三种)...
  3. 数论-朴素卢卡斯(Lucas)模板
  4. php 自定义格式化,PHP自定义函数格式化json数据示例
  5. mysql 主键唯一,MySQL。关键表中的主键。唯一ID还是多个唯一键?
  6. 不插电的计算机科学百度云,【精品】不插电的计算机科学.pdf
  7. ControllerShutdownRequest分析
  8. 解决部分控件,自动获取焦点的情况
  9. ajax响应不显示值,Ajax响应200正常,但显示未能加载响应数据
  10. Nginx-配置https虚拟服务(访问http时自动跳转https)
  11. 计算机音乐组获奖作品,大学生计算机音乐创作类决赛揭晓 浙音6件作品均获奖...
  12. 台服DNF修改Script.pvf文件修改黑钻抽奖机的道具爆率,图文详解
  13. 鸿蒙天钟壁纸,鸿蒙2.0桌面小工具时钟,日历显示不出来
  14. 2021 最新 android studio 阿里 maven 仓库地址 Using insecure protocols with repositories, without explicit op
  15. 软考架构师 | 01 考试介绍及备考攻略
  16. 计算机台式内存条,台式电脑怎么装内存条_台式电脑加装内存条方法-win7之家
  17. 计算机控制技术第二版答案于微波,微波技术习题答案 2.doc
  18. C语言字谜游戏(函数嵌套、指针)
  19. 在Nginx中正确返回HTTPS/SSL错误
  20. 老少恋中的愉悦和不安

热门文章

  1. sketchup 2018下载与安装教程
  2. 桌面快捷方式自动消失
  3. word三线表最后一行加粗方式
  4. 开关电源BOOST升压原理
  5. android无线投屏到电视盒子,【沙发管家】如何把电脑视频投屏到智能电视/ 电视盒子上?...
  6. mysql 用户及权限
  7. 手机号码状态检测(空号检测)的原理
  8. 两天撸一个天气应用微信小程序
  9. steam服务器102修复,分享steam错误代码102的解决方法
  10. Mac 设置终端命令快捷方式