前面的文章介绍了如何用Tarjan算法计算无向图中的e-DCC和v-DCC以及如何缩点。

本篇文章资料参考:李煜东《算法竞赛进阶指南》

这一篇我们讲如何用Tarjan算法求有向图的SCC( 强连通分量 )已经如何缩点。

给定一张有向图,若对于图中任意两个节点x和y,

既有x到y的路径,又有y到x的路径,则该有向图是一张“强连通图”。

有向图的极大连通子图被称为“强连通分量”,即SCC。

一个环一定是强连通图。如果既有x到y的路径,又有y到x的路径,那么x和y就一定在一个环中。

这就是Tarjan算法的原理:对于每个点x,找到与它一起能构成环的所有点。

下面介绍有向图中的三种边(x,y):

1. 树枝边:搜索树中x是y的父节点

2. 前向边:搜索树中x是y的祖先节点

3. 后向边:搜索树中y是x的祖先节点

4. 横叉边:除了以上三种情况外的边,满足dfn[y]<dfn[x]

这里只给出简单定义,不再赘述。

我们可以发现,用Tarjan算法求SCC时,后向边(x,y)可以和搜索树上从y到x的路径构成一个环。

除后向边外,通过横叉边也可能找到一条从y出发能回到x的祖先节点的路径。

那么为了找到通过横叉边和后向边构成的环,Tarjan算法在dfs的过程中维护一个栈,当访问到节点x时,栈中需要保存以下两类节点:

1. 搜索树上x的祖先节点,记为集合anc(x)。设y∈anc(x),若存在一条后向边(x,y),则(x,y)和y到x之间的路径一起形成环。

2. 已经访问过,并且存在一条路径到达anc(x)的节点。

设z时一个这样的点,从z出发存在一条路径到达y∈anc(x)。若存在横叉边(x,y),则(x,z)、z到y的路径、y到x的路径形成一个环。

综上,栈中的节点就是能从x出发点的“后向边”和“横叉边”形成环的节点。

至此,我们引入追溯值low[x]的概念,有向图的Tarjan算法里面的定义和无向图是不一样的。

还是设subtree(x)表示以x为根的子树。x的追溯值low[x]定义为满足一下条件的节点的最小dfn:

1. 该点在栈中 2. 存在一条从subtree(x)出发的有向边,以该点为终点

根据以上定义,Tarjan算法根据以下步骤计算low[x]:

1. 当节点x第一次被访问时,将x入栈,初始化low[x]=dfn[x]

2. 扫描从头x出发的每条边(x,y),若y没被访问过,则说明(x,y)时树枝边,递归访问y,从y回溯之后,令low[x]=min(low[x],low[y]),若y被访问过且y在栈中,令low[y]=min(low[x],dfn[y])

3. 从x回溯之前,判断是否有low[x]=dfn[x],若成立,则不断从栈中弹出节点直至x出栈。

SCC的判定法则:

在上面的计算步骤3中,从栈中从x到栈顶的所有节点构成一个SCC。

少废话,上代码!

好der~

#include<bits/stdc++.h>
#define N 1000010
using namespace std;
inline int read(){
int data=,w=;char ch=;
while(ch!='-' && (ch<''||ch>''))ch=getchar();
if(ch=='-')w=-,ch=getchar();
while(ch>='' && ch<='')data=data*+ch-'',ch=getchar();
return data*w;
}
struct Edge{
int nxt,to;
#define nxt(x) e[x].nxt
#define to(x) e[x].to
}e[N<<];
int head[N],tot=;
inline void addedge(int f,int t){
nxt(++tot)=head[f];to(tot)=t;head[f]=tot;
}
int dfn[N],low[N],stk[N],ins[N],c[N];
vector<int> scc[N];
int n,m,cnt,top,num;
void tarjan(int x){
dfn[x]=low[x]=++cnt;
stk[++top]=x,ins[x]=;
for(int i=head[x];i;i=nxt(i)){
int y=to(i);
if(!dfn[y]){
tarjan(y);
low[x]=min(low[x],low[y]);//搜索树上的点
}else if(ins[y])
low[x]=min(low[x],dfn[y]);//y在栈中且y被访问过了
   }
  if(dfn[x]==low[x]){
num++;int z;//新的一个SCC
do{
z=stk[top--],ins[z]=;//弹出栈顶元素z
c[z]=num,scc[num].push_back(z);//z插入存第num个SCC的vector里
}while(z!=x);//直到x被弹出栈
}
}
int main(){
n=read();m=read();
for(int i=;i<=m;i++){
int x=read(),y=read();
addedge(x,y);
}
for(int i=;i<=n;i++)
if(!dfn[i])tarjan(i);
for(int i=;i<=num;i++){
printf("%d:",i);
for(int j=;j<scc[i].size();j++){
printf(" %d",scc[i][j]);
}
putchar();
}
return ;
}

SCC的缩点就非常简单了,上面我们已经用c[x]储存了每个点所在的SCC的编号,那我们直接类似e-DCC的缩点,把每个SCC缩成一个点,若c[x]≠c[y],我们就在编号为c[x]和c[y]的SCC中连一条边就可以得到一个有向无环图( DAG )。

代码真的非常简单,甚至不需要再跑一遍dfs。

给出代码:

for(int x=;x<=n;x++)
for(int i=head[x];i;i=nxt(i)){
int y=to(i);
if(c[x]==c[y])continue;
addedge_c(c[x],c[y]);
}
//够简单了吧...

整个程序的代码我就不贴出来了,建新图和我前面e-DCC缩点的博客完全一致。

下一篇讲点数学,别忘了来听课。

[Tarjan系列] Tarjan算法与有向图的SCC的更多相关文章

  1. [Tarjan系列] Tarjan算法求无向图的桥和割点

    RobertTarjan真的是一个传说级的大人物. 他发明的LCT,SplayTree这些数据结构真的给我带来了诸多便利,各种动态图论题都可以用LCT解决. 而且,Tarjan并不只发明了LCT,他对 ...

  2. [Tarjan系列] Tarjan算法求无向图的双连通分量

    这篇介绍如何用Tarjan算法求Double Connected Component,即双连通分量. 双联通分量包括点双连通分量v-DCC和边连通分量e-DCC. 若一张无向连通图不存在割点,则称它为 ...

  3. Tarjan算法求有向图强连通分量并缩点

    // Tarjan算法求有向图强连通分量并缩点 #include<iostream> #include<cstdio> #include<cstring> #inc ...

  4. [Tarjan系列] 无向图e-DCC和v-DCC的缩点

    上一篇讲了如何应用Tarjan算法求出e-DCC和v-DCC. 那么这一篇就是e-DCC和v-DCC的应用之一:缩点. 先讲e-DCC的缩点. 我们把每一个e-DCC都看成一个节点,把所有桥边(x,y ...

  5. Kosaraju 算法检测有向图的强连通性

    给定一个有向图 G = (V, E) ,对于任意一对顶点 u 和 v,有 u --> v 和 v --> u,亦即,顶点 u 和 v 是互相可达的,则说明该图 G 是强连通的(Strong ...

  6. HDU 1269 -- 迷宫城堡【有向图求SCC的数目 &amp;&amp; 模板】

    迷宫城堡 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Submi ...

  7. 【数据结构与算法】多种语言(VB、C、C#、JavaScript)系列数据结构算法经典案例教程合集目录

    目录 1. 专栏简介 2. 专栏地址 3. 专栏目录 1. 专栏简介 2. 专栏地址 「 刘一哥与GIS的故事 」之<数据结构与算法> 3. 专栏目录 [经典回放]多种语言系列数据结构算法 ...

  8. Tarjan系列算法总结(hdu 1827,4612,4587,4005)

    tarjan一直是我看了头大的问题,省选之前还是得好好系统的学习一下.我按照不同的算法在hdu上选题练习了一下,至少还是有了初步的认识.tarjan嘛,就是维护一个dfsnum[]和一个low[],在 ...

  9. tarjan算法-解决有向图中求强连通分量的利器

    小引 看到这个名词-tarjan,大家首先想到的肯定是又是一个以外国人名字命名的算法.说实话真的是很佩服那些算法大牛们,佩服得简直是五体投地啊.今天就遇到一道与求解有向图中强连通分量的问题,我的思路就 ...

随机推荐

  1. mysql按关键词截取字符串

    按关键字截取字符串 :substring_index(被截取字段,关键字,关键字出现的次数) eg:字符串:test.docx:test2.zip substring_index(file_name, ...

  2. Ubuntu+docker+gitlab安装和使用

    以前自己写的代码都是在本地,因为都是自己一个人维护,现在交给团队维护了,所以想着搭建一个gitlab 1,拉镜像 安装非常简单 docker search gitlab  搜索镜像 docker pu ...

  3. jmeter入门(环境搭建&运行&初识)

    最近了解了一些性能测试的基础知识和原理决定动手实践下,比较选择了jmeter 一.什么是jmeter Apache JMeter是Apache组织开发的基于Java的压力测试工具.用于对软件做压力测试 ...

  4. jenkins自动化部署项目3 --设置用户

    我直接设置的admin ,jenkins可以新建多个用户,并赋予不同的权限(TODO) 等后续需要严格规范操作人的时候再补充

  5. Selenium+python操作id为动态变化的frame(iframe)

    先定位到一组frame:ele = dr.find_elements_by_tag_name('iframe')此时获得一组frame 再通过index取需要切进去的frame并取到该frame的id ...

  6. 2019-2020-1 20199303 《Linux内核原理分析》 第一周作业

    2019-2020-1 20199303 <Linux内核原理分析> 第一周作业 1. 环境准备 在众多的Linux发行版中,Ubuntu,小红帽还有类Unix系统的BSD系统,我选择了目 ...

  7. 阿里云搭建nginx + uWSGI 实现 django 项目

    系统版本 CentOS/7 64位 1.安装使用python3 创建python3目录 sudo mkdir /usr/local/python3 进入python3目录 cd /usr/local/ ...

  8. android实现emoji输入

    学android也有一段时间, 一直都是自己摸索, 各种上网查资料, 也明白了不能一味去索取有时间也要分享一些自己的心得 . 最近几天都在写关于android emoji输入的小例子,网上有不少源码还 ...

  9. docker容器添加对外映射端口

    一般在运行容器时,我们都会通过参数 -p(使用大写的-P参数则会随机选择宿主机的一个端口进行映射)来指定宿主机和容器端口的映射,例如 docker run -it -d --name [contain ...

  10. Python 正则re匹配中文、英式数字

    #coding:utf-8 import re s = u''' 或多或少的好好读书电锯惊魂20202 和水电费后是否会时候1212没收到风10.12海大富的是粉红色的和办法的1244525.000 ...