Tarjan算法与无向图的连通性

1:基础概念

在说Tarjan算法求解无向图的连通性之前,先来说几个概念:

<1. 时间戳:在图的深度优先遍历中,按照每一个结点第一次被访问到的时间顺序,依次给予N个结点1~N的整数边集,该标记就被计位“时间戳”,计做 \(dfn[x]\)。

<2. 搜索树:任选一个结点深度优先遍历,每个点只访问一次。产生递归的边构成的树为搜索树。

❤️. \(subtree(x)\):搜索树中以x为根的子树。

<4. 追溯值:追溯值(\(low[x]\))定义为以下结点的时间戳的最小值,这些结点满足:

①:是\(subtree(x)\)的结点; ②:通过一条不在搜索树上的边,能够到达\(subtree(x)\)的结点

在了解概念之后,我们可以根据定义来计算\(low[x]\):

令\(low[x]=dfn[x]\);然后考虑每个从x的出边( x, y ),如果x是y的父节点,则\(low[x]=min(low[x],low[y])\);如果边不是搜索树上的边,则令\(low[x]=min(lwo[x],dfn[y])\); //ps:后面代码也会提到

2:Tarjan判断割点

判定定理:对于一个点 x ;如果x不为根节点,那么x是割点当且仅当搜索树上存在x 的一个子节点y,满足:

\[dfn[x]<=low[y]
\]

如果x是根节点,那么搜索树上至少存在两个子节点满足上述性质。

代码模板(求割点个数,及从小到大输出):

#include<bits/stdc++.h>

using namespace std;
const int maxn=1e5+10;
int head[maxn],n,m,tot;
struct Edge
{
int nex,to;
}edge[maxn<<1];
void add(int from,int to)
{
edge[++tot].to=to;
edge[tot].nex=head[from];
head[from]=tot;
}
int dfn[maxn],low[maxn],st[maxn],idx,root;
bool cut[maxn];
void tarjan(int u) //tarjan求一下dfn和low
{
dfn[u] = low[u] = ++idx; //初始化dfn和low
int flag=0;
for(int i=head[u];i!=-1;i=edge[i].nex)
{
int v=edge[i].to;
if(dfn[v]) low[u]=min(low[u],dfn[v]); //定义,追溯值为子树最小的时间戳
else{
tarjan(v); //找子节点的子节点
low[u]=min(low[u],low[v]);
if(dfn[u]<=low[v]){ //判定条件
flag++;
if(u!=root||flag>1) cut[u]=true;
}
}
}
} int main()
{
scanf("%d %d",&n,&m);
memset(head,-1,sizeof(head));
tot=0;
for(int i=1;i<=m;++i){
int a,b;
scanf("%d %d",&a,&b);
if(a==b) continue;
add(a,b);
add(b,a);
}
for(int i=1;i<=n;++i){
if(!dfn[i]){
root=i;
tarjan(i);
}
}
int res=0; //个数
for(int i=1;i<=n;++i)
if(cut[i]) res++;
printf("%d\n",res);
for(int i=1;i<=n;++i)
if(cut[i]) printf("%d ",i);
printf("\n");
}

如果要求连通分量数,还要写个dfs即可

void dfs(int u)
{
vis[u]=1;
for(int i=head[u];i!=-1;i=edge[i].nex){
int v=edge[i].to;
if(vis[v]) continue;
vis[v]=1;
dfs(v);
}
}
for(int i=1;i<=cnt;++i)
{
if(cut[i])
{
int son=0;
memset(vis,0,sizeof(vis));
vis[i]=1;
for(int j=head[i];j!=-1;j=edge[j].nex){
int v=edge[j].to;
if(vis[v]) continue;
dfs(v);
son++;
}
printf("%d\n",son); //son为连通分量数
}
}

3:Tarjan判断割边

割边判断法则:无向边(x,y)是桥当且仅当搜索树上存在 x 的一个子节点 y ,满足:

\[dfn[x]<low[y]
\]

与割点不同的是,这里不能取等号。这个不等式其实也很好理解,根据定义\(dfn[x]<low[y]\)说明从\(subtree(y)\)出发,在不经过边\((x,y)\)的前提下,不管走哪条边,都无法到达x或比x更早访问的结点。若把边\((x,y)\)删除,则\(subtree(y)\)就像是形成了一个封闭环境,因此边\((x,y)\)是割边。

#include<bits/stdc++.h>

using namespace std;
const int maxn=1e5+10;
struct Edge
{
int nex,to;
}edge[maxn<<1];
int n,m,tot,head[maxn];
void add(int from,int to) //这里边从下标为2开始存方便异或处理
{
edge[++tot].to=to;
edge[tot].nex=head[from];
head[from]=tot;
}
int bridge[maxn<<1],dfn[maxn],low[maxn],idx;
void tarjan(int u,int fa)
{
dfn[u]=low[u]=++idx;
for(int i=head[u];i!=-1;i=edge[i].nex)
{
int v=edge[i].to;
if(!dfn[v]){
tarjan(v,i);
low[u]=min(low[u],low[v]);
if(dfn[u]<low[v]) bridge[i]=bridge[i^1]=true;
}
else if(i!=(fa^1)) low[u]=min(low[u],dfn[v]);
}
} int main()
{
scanf("%d %d",&n,&m);
memset(head,-1,sizeof(head));
tot=1; //注意边的下标从2开始储存
for(int i=1;i<=m;++i){
int a,b;
scanf("%d %d",&a,&b);
add(a,b);
add(b,a);
}
for(int i=1;i<=n;++i)
if(!dfn[i]) tarjan(i,-1);
for(int i=2;i<=tot;i+=2)
if(bridge[i]) printf("%d %d\n",edge[i].to,edge[i^1].to);
system("pause");
}

【题意】:给了一幅图,问需要加几条边将这幅图所有的结点都在环上(可能在不同的环上)

求一下割边,判断一下叶结点的数量,答案即为(num+1)>>1;看代码:

#include<cstdio>
#include<cstring>
#include<algorithm> using namespace std;
const int maxn=1e4+10;
int head[maxn],tot;
struct Edge
{
int from,nex,to;
}edge[maxn<<1];
void add(int from,int to)
{
edge[++tot].to=to;
edge[tot].from=from;
edge[tot].nex=head[from];
head[from]=tot;
}
int dfn[maxn],low[maxn],idx;
bool bridge[maxn];
void tarjan(int u,int fa) //求割边(桥)
{
dfn[u]=low[u]=++idx;
for(int i=head[u];i!=-1;i=edge[i].nex)
{
int v=edge[i].to;
if(!dfn[v]){
tarjan(v,i);
low[u]=min(low[u],low[v]);
if(dfn[u]<low[v]){
bridge[i]=bridge[i^1]=true;
}
}
else if(i!=(fa^1)) low[u]=min(low[u],dfn[v]); //异或打括号!!
}
}
int n,m,outd[maxn],id[maxn],dcc; //id为双连通分量编号,outd为出度
void dfs(int u) //跑一遍dfs求一下各个边的双连通分量编号
{
id[u]=dcc;
for(int i=head[u];i!=-1;i=edge[i].nex)
{
int v=edge[i].to;
if(bridge[i]||id[v]) continue; //双连通分量不包括桥
dfs(v);
}
} int main()
{
scanf("%d %d",&n,&m);
tot=1;
memset(head,-1,sizeof(head));
for(int i=1;i<=m;++i){
int a,b;
scanf("%d %d",&a,&b);
add(a,b);
add(b,a);
}
for(int i=1;i<=n;++i)
if(!dfn[i]) tarjan(i,-1);
for(int i=1;i<=n;++i)
if(!id[i]){
dcc++;
dfs(i);
}
for(int i=2;i<=tot;i+=2)
{
int u=edge[i].from,v=edge[i].to;
if(id[u]!=id[v]){
outd[id[u]]++;
outd[id[v]]++;
}
}
int ans=0; //叶子结点数量
for(int i=1;i<=dcc;++i)
if(outd[i]==1) ans++;
printf("%d\n",(ans+1)>>1);
}

Tarjan算法与割点割边的更多相关文章

  1. tarjan算法(割点/割边/点连通分量/边连通分量/强连通分量)

    tarjan算法是在dfs生成一颗dfs树的时候按照访问顺序的先后,为每个结点分配一个时间戳,然后再用low[u]表示结点能访问到的最小时间戳 以上的各种应用都是在此拓展而来的. 割点:如果一个图去掉 ...

  2. Tarjan算法求割点

    (声明:以下图片来源于网络) Tarjan算法求出割点个数 首先来了解什么是连通图 在图论中,连通图基于连通的概念.在一个无向图 G 中,若从顶点i到顶点j有路径相连(当然从j到i也一定有路径),则称 ...

  3. Tarjan 算法求割点、 割边、 强联通分量

    Tarjan算法是一个基于dfs的搜索算法, 可以在O(N+M)的复杂度内求出图的割点.割边和强联通分量等信息. https://www.cnblogs.com/shadowland/p/587225 ...

  4. 学习笔记--Tarjan算法之割点与桥

    前言 图论中联通性相关问题往往会牵扯到无向图的割点与桥或是下一篇博客会讲的强连通分量,强有力的\(Tarjan\)算法能在\(O(n)\)的时间找到割点与桥 定义 若您是第一次了解\(Tarjan\) ...

  5. tarjan算法应用 割点 桥 双连通分量

    tarjan算法的应用. 还需多练习--.遇上题目还是容易傻住 对于tarjan算法中使用到的Dfn和Low数组. low[u]:=min(low[u],dfn[v])--(u,v)为后向边,v不是u ...

  6. tarjan算法求割点cojs 8

    tarjan求割点:cojs 8. 备用交换机 ★★   输入文件:gd.in   输出文件:gd.out   简单对比时间限制:1 s   内存限制:128 MB [问题描述] n个城市之间有通讯网 ...

  7. 『Tarjan算法 无向图的割点与割边』

    无向图的割点与割边 定义:给定无相连通图\(G=(V,E)\) 若对于\(x \in V\),从图中删去节点\(x\)以及所有与\(x\)关联的边后,\(G\)分裂为两个或以上不连通的子图,则称\(x ...

  8. Tarjan算法——强连通、双连通、割点、桥

    Tarjan算法 概念区分 有向图 强连通:在有向图\(G\)中,如果两个顶点\(u, v\ (u \neq v)\)间有一条从\(u\)到\(v\)的有向路径,同时还有一条从\(v\)到\(u\)的 ...

  9. 无向连通图求割点(tarjan算法去掉改割点剩下的联通分量数目)

    poj2117 Electricity Time Limit: 5000MS   Memory Limit: 65536K Total Submissions: 3603   Accepted: 12 ...

随机推荐

  1. JS中的字符串可以直接调用字符串对象的属性和方法

    在JS中,会自动在字符串与字符串对象之间进行转换,因此,任何一个字符串常量都可以看作是一个String对象,其可以直接作为对象使用,只要在字符串变量的后面加 “.” 便可以直接调用String对象的属 ...

  2. Spring中如何使用工厂模式实现程序解耦?

    目录 1. 啥是耦合.解耦? 2. jdbc程序进行解耦 3.传统dao.service.controller的程序耦合性 4.使用工厂模式实现解耦 5.工厂模式改进 6.结语 @ 1. 啥是耦合.解 ...

  3. 安装和配置Linux系统虚拟机

    1.打开虚拟机软件 2.点击创建新的虚拟机,选择典型(推荐)类型的配置. 3.点击稍后安装操作系统. 4.客户机操作系统选择Linux,版本选择CentOS 7 64位. 5.虚拟机名称可自行更改,位 ...

  4. cordova将vue项目打包成apk

    1,若vue项目不在cordova项目里,直接把它复制进来,避免改动代码的麻烦 2,直接按照以下链接进行操作即可 链接:https://www.cnblogs.com/qirui/p/8421372. ...

  5. BZOJ - 2038 小Z的袜子(普通莫队)

    题目链接:小Z的袜子 题意:$n$只袜子,$m$个询问,每次回答有多大概率在$[L,R]$区间内抽到两只颜色相同的袜子 思路:普通莫队,如果两个询问左端点在一个块内,则按询问右端点排序,否则按照所在块 ...

  6. 【C语言】创建一个函数,利用该函数将字符串中的小写字母转换为大写字母

    原理: 这类题目主要通过ASCII(美国信息交换标准代码)码差值实现,A对应ASCII码十进制数字是65,a对应ASCII码十进制数字是97,即大小写字母之间ASCII码差值为32,想要将大写字母转换 ...

  7. 【C语言】判断某一正整数是否为完数

    什么是完数? 如果一个数等于它的因子之和,则称该数为“完数”(或“完全数”). 例如,6的因子为1.2.3,而 6=1+2+3,因此6是“完数”. 程序框图:m  问题分析 根据完数的定义,解决本题的 ...

  8. springmvc实现文件下载

    springmvc实现文件下载 使用springmvc实现文件下载有两种方式,都需要设置response的Content-Disposition为attachment;filename=test2.p ...

  9. 201771010135杨蓉庆《面向对象程序设计(java)》第二周学习总结

    第一部分:理论知识学习部分 3.1 标识符:由字母.下划线.美元符号和数字组成, 且第一个符号不能为数字,可用作:类名.变量名.方法名.数组名.文件名等.有Hello.$1234.程序名.www_12 ...

  10. Spring学习(十)

    需要的jar包 1.Spring核心必须依赖的库:commons-logging-1.1.1.jar2.Spring IoC部分核心库: spring-beans-4.3.9.RELEASE.jar ...