RobertTarjan真的是一个传说级的大人物。

他发明的LCT,SplayTree这些数据结构真的给我带来了诸多便利,各种动态图论题都可以用LCT解决。

而且,Tarjan并不只发明了LCT,他对计算机科学做出的贡献真的很多。

这一篇我就来以他名字命名的Tarjan算法可以O(n)求出无向图的割点和桥。

进一步可以求出无向图的DCC( 双连通分量 )。不止无向图,Tarjan算法还可以求出有向图的SCC( 强连通分量 )。

Tarjan算法基于dfs,接下来我们引入几个基本概念。

dfn:时间戳

我们对一张图进行深度优先遍历,根据第一次访问到它的时间顺序给它打上一个标记,这个标记就是时间戳。

搜索树:

在一张无向连通图中选定任意一个节点进行深度优先遍历,每个点仅访问一次。所有发生了递归的边会构成一棵树,我们称其为无向连通图的“搜索树”。

追溯值:

除了时间戳,Tarjan算法还引入了另一个概念:“追溯值” low。

我们用subtree(x)表示搜索树中以x为根的子树,low[x]定义为下列节点的时间戳的最小值:

1. subtree(x)中的节点 2. 通过一条不在搜索树上的边,能够到达subtree(x)中的节点

我们来画一个图理解一下:(方便起见,图中的节点编号就是它的时间戳)

图中红色的边就是这张图的搜索树

那么我们容易得出:subtree(2)={4,3},5可以通过不在搜索树上的边到达subtree(2)。

所以,low[2]=min{dfn[4],dfn[3],dfn[5]},得出low[2]=3。

根据定义来计算low[x]的方法就非常明显了。因为subtree(x)包括x,所以先令low[x]=dfn[x]。

然后遍历从x出发的每一条边(x,y),计算low[x]。

接下来给出无向图的桥和割点判定法则。

无向边(x,y)是桥,当且仅当x在搜索树上的一个子节点y满足low[y]>dfn[x]。

若x不是搜索树的根节点,则x是割点当且仅当搜索树上的一个子节点y满足low[y]>=dfn[x]。

若x是根节点,则x是割点当且仅当搜索树上存在至少两个x的子节点y1,y2满足上式。

桥边有以下性质:

1. 桥一定是搜索树中的边 2. 一个简单环中边都不是桥边

一个环被称为简单环当且仅当其包含的所有点都只在这个环中被经过了一次。

扩展内容:这里给出用dfs求出一个图中所有简单环的代码

int cnt;
void dfs(int u){
dfn[u]=++cnt;
for(int i=head[u];i;i=nxt(i)){
int v=to(i);
if(v==fa[u])continue;
if(!dfn[v])fa[v]=u,dfs(v);
else if(dfn[u]<dfn[v]){
printf("%d",v);
do{
printf(" %d",fa[v]);v=fa[v];
}while(v!=u);
//找完一个环了
putchar('\n');
}
}
}

这个作为扩展内容就不再展开叙述。

下面给出求出无向图中所有的桥的代码:

#include<bits/stdc++.h>
#define N 100010
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 dfn[N],low[N],tot=;//储存边的编号,由于要用^1找反向边,从1开始
int bridge[N],head[N];
int n,m,cnt;
void addedge(int f,int t){
nxt(++tot)=head[f];to(tot)=t;head[f]=tot;
}
void tarjan(int x,int in_edge){//in_edge表示递归进入每个节点的边的编号
dfn[x]=low[x]=++cnt;
for(int i=head[x];i;i=nxt(i)){
int y=to(i);
if(!dfn[y]){
tarjan(y,i);
low[x]=min(low[x],low[y]);//在搜索树上的边
if(low[y]>dfn[x])//桥判定法则
bridge[i]=bridge[i^]=;//这条边和它的反向边都是桥
}else if(i!=(in_edge^))
low[x]=min(low[x],dfn[y]);//不在搜索树上的边
}
}
int main(){
n=read();m=read();
for(int i=;i<=m;i++){
int x=read(),y=read();
addedge(x,y);addedge(y,x);
}
for(int i=;i<=n;i++)
if(!dfn[i])tarjan(i,);
for(int i=;i<tot;i+=)
if(bridge[i])
printf("%d %d\n",to(i^),to(i));//输出桥两边的点
}

以上就是求无向图中所有桥的程序了,可以自己画图模拟一下tarjan算法的流程加深理解。

下面给出求无向图中所有割点的程序:

这里需要注意的是,由于割点判定法则是小于等于号,所以不需要考虑父节点和重边的问题,所有dfn[x]都可以用来更新low[x]

#include<bits/stdc++.h>
#define N 100010
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],dfn[N],low[N],rt,tot=,n,m,cnt;
int cut[N];
inline void addedge(int f,int t){
nxt(++tot)=head[f];to(tot)=t;head[f]=tot;
}
void tarjan(int x){
dfn[x]=low[x]=++cnt;
int flag=;
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]);
if(low[y]>=dfn[x]){//就一个割点判断法则没必要解释什么了吧?
flag++;
if(x!=rt||flag>)cut[x]=;
}
}else low[x]=min(low[x],dfn[y]);
}
}
int main(){
n=read();m=read();
for(int i=;i<=m;i++){
int x=read(),y=read();
if(x==y)continue; //自环直接判掉好吧,不多bb
addedge(x,y);addedge(y,x);
}
for(int i=;i<=n;i++)
if(!dfn[i])rt=i,tarjan(i);//无向图不一定连通,对每一个连通块都要跑一发tarjan
for(int i=;i<=n;i++)
if(cut[i])printf("%d ",i);
return ;
}

桥边判定法则和割点判定法则后面会update上,这一篇暂时更到这里,下一篇讲e-DCC和v-DCC的求法

主要是联赛在即,更新尽量多的算法扎实自己基础才要紧些...请多见谅

[Tarjan系列] Tarjan算法求无向图的桥和割点的更多相关文章

  1. tarjan算法求无向图的桥、边双连通分量并缩点

    // tarjan算法求无向图的桥.边双连通分量并缩点 #include<iostream> #include<cstdio> #include<cstring> ...

  2. Light OJ - 1026 - Critical Links(图论-Tarjan算法求无向图的桥数) - 带详细注释

     原题链接   无向连通图中,如果删除某边后,图变成不连通,则称该边为桥. 也可以先用Tajan()进行dfs算出所有点 的low和dfn值,并记录dfs过程中每个 点的父节点:然后再把所有点遍历一遍 ...

  3. Hdu 4738【tanjan求无向图的桥】割边判定定理 dfn[x] < low[y]

    题目: 曹操在长江上建立了一些点,点之间有一些边连着.如果这些点构成的无向图变成了连通图,那么曹操就无敌了.刘备为了防止曹操变得无敌,就打算去摧毁连接曹操的点的桥.但是诸葛亮把所有炸弹都带走了,只留下 ...

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

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

  5. tarjan算法--求无向图的割点和桥

    一.基本概念 1.桥:是存在于无向图中的这样的一条边,如果去掉这一条边,那么整张无向图会分为两部分,这样的一条边称为桥无向连通图中,如果删除某边后,图变成不连通,则称该边为桥. 2.割点:无向连通图中 ...

  6. hdu 4738 Caocao's Bridges 求无向图的桥【Tarjan】

    <题目链接> 题目大意: 曹操在长江上建立了一些点,点之间有一些边连着.如果这些点构成的无向图变成了连通图,那么曹操就无敌了.周瑜为了防止曹操变得无敌,就打算去摧毁连接曹操的点的桥.但是诸 ...

  7. [Tarjan系列] Tarjan算法与有向图的SCC

    前面的文章介绍了如何用Tarjan算法计算无向图中的e-DCC和v-DCC以及如何缩点. 本篇文章资料参考:李煜东<算法竞赛进阶指南> 这一篇我们讲如何用Tarjan算法求有向图的SCC( ...

  8. 蓝桥杯历届试题 危险系数(dfs或者并查集求无向图关于两点的割点个数)

    Description 抗日战争时期,冀中平原的地道战曾发挥重要作用. 地道的多个站点间有通道连接,形成了庞大的网络.但也有隐患,当敌人发现了某个站点后,其它站点间可能因此会失去联系. 我们来定义一个 ...

  9. SPF Tarjan算法求无向图割点(关节点)入门题

    SPF 题目抽象,给出一个连通图的一些边,求关节点.以及每个关节点分出的连通分量的个数 邻接矩阵只要16ms,而邻接表却要32ms,  花费了大量的时间在加边上. //   time  16ms 1 ...

随机推荐

  1. centos7 - nginx配置安装phpmyadmin

    原文  https://www.linuxidc.com/Linux/2017-10/147556.htm 使用Nginx搭建phpMyAdmin   Nginx有什么用? Nginx可读作Engin ...

  2. /proc/sys/kernel/sysrq /proc/sysrq-trigger----强制重启/触发器

    LINUX远程强制重启/proc/sys/kernel/sysrq /proc/sysrq-trigger----触发器 ttp://blog.csdn.net/beckdon/article/det ...

  3. 模型压缩-L1-norm based channel pruning(Pruning Filters for Efficient ConvNets)

    论文笔记——PRUNING FILTERS FOR EFFICIENT CONVNETS  转载:https://www.cnblogs.com/zhonghuasong/p/7642000.html ...

  4. shell脚本中用到的计算

    在shell脚本中计算一般会涉及到let.$(()).$[].bc(另扩展expr).其中let.$(()).$[]都是用来做基本整数运算,bc可以用来做浮点运算. (1).let.$(()).$[] ...

  5. double,float,BigDecimal类型数值的操作

    float四舍五入保留两位小数 /** * float四舍五入保留两位小数 * */ public static float formatDecimal(float n) { return (Math ...

  6. 通过举例了解java中的流

    Java流结构介绍: Java所有的流类位于java.io包中,都分别继承字以下四种抽象流类型.   字节流 字符流 输入流 InputStream Reader 输出流 OutputStream W ...

  7. pcntl_fork()函数说明

    pcntl_fork()函数复制了当前进程的PCB,并向父进程返回了派生子进程的pid,父子进程并行,打印语句的先后完全看系统的调度算法,打印的内容控制则靠pid变量来控制.因为我们知道pcntl_f ...

  8. redis watch 加 事务实现秒杀

    <?php   //redis watch 加 事务实现秒杀  $redis = new redis();  $result = $redis->connect('10.10.10.119 ...

  9. web框架之socket

    概述 套接字最初是为同一主机上的应用程序所创建,使得主机上运行的一个程序(又名一个进程)与另一个运行的程序进行通信.这就是所谓的进程间通信 ( Inter Process Communication, ...

  10. python-Web-flask-数据库

    3 数据库: Flask-SQLAlchemy 安装及连接 pip install flask-sqlalchemy pip install flask-mysqldb # 数据库链接地址 app.c ...