学习有向图和无向图的强连通分量(基本概念+割点+点双联通分量+桥+边双连通分量+全套模板【Tarjan】)

最近总是考到Tarjan,让我措手不及

  • 基本概念
  • 割点以及点双连通分量
  • Tarjan法求割点
    • 推导过程
    • 代码实现
  • Tarjan法求点双连通分量
    • 推导过程
    • 代码实现
  • 有向图的Tarjan缩点
  • 桥与边双连通分量
  • Tarjan法求桥
    • 理论推导
    • 代码实现
  • Tarjan法求边双连通分量
    • 理论推导
    • 代码实现

前言:有向图和无向图其实并没有太多的差别,这里就没有必要把一些东西做无意义的重复
我就只写了无向图的,遇到了有区别在下面的阐释中会有提示

基本概念

无向图:边没有方向(或者称为双向)的图
连通图:如果一个图至少有两个点,那么图任意两个点可以互相到达
子图:如果图G’的点集是图G的点集的子集,且图G’的边集是图G的边集的子集,称G’是G的子图
连通子图:如果G的子图G’是连通图,那么G’就是G的一个连通子图
极大连通子图:如果图G’是G的连通子图,并且不存在图G的另一个连通子图G’‘使得G’是G’‘的子图,称G’为G的极大连通子图,又叫连通分量
极小连通子图:如果图G’是G的连通子图,并且不存在图G的另一个连通子图G’‘使得G’不是G’'的子图,称G’为G的极小连通子图

如果无向图G是连通图,那么G只有一个极大连通子图(连通分量)即它本身,
同时G一定有极小连通子图即它的最小生成树并可能有多个

如果无向图G不是连通图,那么G有多个极大连通子图(连通分量),
同时G没有极小连通子图。

极大和极小是指集合上的(会不会被其他集合包含)而不是单纯指点或边的数量
好好理解吧实在看不懂其实也无所谓,这种死概念在这里插入图片描述


割点以及点双连通分量

割点:如果去掉无向连通图G中一点x,并去掉所有与x相邻的边,余下的点和边构成的子图G’不再是连通图,那么称x是G的一个割点
点双连通图:如果一个连通图不存在割点,那么可以称之为点双连通图
点双连通分量:如果一个连通分量不存在割点,那么可以称之为点双连通分量
在这里插入图片描述
如图中,c和d就是割点,如果去掉c以及c所对应的所有边,意味着G无法与其他的点联通

A,B,C,D构成了一个点双连通分量;D,E,F也是一个;任意去掉一个点,剩下的点还是连通
C,G并没有构成一个点双连通分量,因为一旦去掉C或者G,就只剩下一个点了,显然并不符合连通图的定义


Tarjan法求割点

推导过程

我们选择图中任意一点u为起点进行dfs,形成一颗搜索树,显然u是搜索树的根
如果x是图的割点,我们考虑x要满足哪些条件:
1.如果x是u,那么x至少有两个子节点
2.如果x不是u,那么x有子节点,
x存在子节点无法在不经过x的情况下到达以x为根的子树外
也就是说至少存在一个点如果要找到一个不属于x的子树的点,必须经过x
那么x就是一个割点,一旦被删掉,那个点无法走到外面的点


解决方案如下:
情况1:我们很容易判断
情况2:我们可以借助tarjan算法中的dfn和low信息来判断
dfn:表示i点在搜索树上的搜索序
low:表示i通过各种边能达到的整棵树深度最小的点

首先我们知道,如果x的某个子节点y能在不经过x的前提下到达以x为根的子树外的点z,那么z的dfn一定小于x
如果low[y]小于dfn[x],就能说明点y能不通过x到达以x为根的子树外
如果存在任意一个y不满足,就说明x是割点
在这里插入图片描述


代码实现

fa是搜索树上u的父节点,cnt是当前的时间戳,child是搜索树上u的子节点数量,
vec是记录了u邻边的vector,isCut[i]表示i是否是割点。
注意当v是已访问过的点时,是取v的dfn更新u的low

void tarjan ( int u, int fa ) {low[u] = dfn[u] = ++ cnt;int child = 0;for ( int i = 0;i < G[u].size();i ++ ) {int v = G[u][i];if ( ! dfn[v] ) {child ++;tarjan ( v, u );if ( low[v] >= dfn[u] )//v能走到的深度最小的点都比u大//说明v无法不经过u到达除开u子树外的点isCut[u] = 1;low[u] = min ( low[v], low[u] );//更新能到达的最小的深度的点}else if ( dfn[u] > dfn[v] && v != fa )///v一定不能是u的父亲//v的深度如果比u小,意味着v先被遍历,v就变成了u祖先级别//属于除开u所在子树外的点,证明u可以不经过自己的父亲直接到达vlow[u] = min ( dfn[v], low[u] );}if ( fa < 0 && child == 1 )//这棵树根只有一个儿子 是会被误判成割点的isCut[u] = 0;
} 

Tarjan法求点双连通分量

推导过程

根据点双连通分量的定义,我们可以很简单的想到以下方法来求点双连通分量:
1.我们将点按照访问顺序入栈
2.当我们确定x是割点,即x的某个子节点y满足low[y]≥dfn[x]时,
我们将栈中的点依次弹出,直到栈顶为x,
x和我们弹出的这些点构成了一个点双连通分量
注意:x不能弹出,因为x可能属于多个点双连通分量
3.如果x是根,即使不是割点也作如上处理

代码实现

st是按访问顺序储存点的栈,cntd是点双连通分量的数量,vecd[i]是储存第i个点双连通分量里点的vector

void tarjan ( int u, int fa ) {low[u] = dfn[u] = ++ cnt;int child = 0;st.push ( u ); for ( int i = 0;i < G[u].size();i ++ ) {int v = G[u][i];if ( ! dfn[v] ) {child ++;tarjan ( v, u );if ( low[v] >= dfn[u] ) {isCut[u] = 1;cntd ++;vecd[cntd].push_back ( u );while ( st.top() != u ) {vecd[cntd].push_back ( st.top() );st.pop();}}low[u] = min ( low[v], low[u] );}else if ( dfn[u] > dfn[v] && v != fa )low[u] = min ( dfn[v], low[u] );}if ( fa < 0 && child == 1 ) isCut[u] = 0;if ( fa < 0 && child == 0 ) {//这棵树只有根节点cntd ++;vecd[cntd].push_back ( u );}
} 

在这里插入图片描述

有向图的Tarjan缩点

我一般都是这么写的,还挺不错的

void tarjan ( int u ) {dfn[u] = low[u] = ++ cnt;sta.push ( u );for ( int i = 0;i < G[u].size();i ++ ) {int v = G[u][i];if ( ! dfn[v] ) {tarjan ( v );low[u] = min ( low[u], low[v] );}else if ( ! sccno[v] )low[u] = min ( low[u], dfn[v] );}if ( low[u] == dfn[u] ) {int v;scc ++;do {v = sta.top();sta.pop();sccno[v] = scc;//顺便标记了v属于哪一个强连通分量vecd[scc].push_back( v );}while ( v != u );}
}

桥与边双连通分量

割边:如果去掉无向连通图G中一条边w,余下的点和边构成的子图G’不再是连通图,那么称w是G的一条割边,也成为桥
边双连通图:如果一个连通图不存在桥,那么可以称之为边双连通图
边双连通分量:如果一个连通分量不存在桥,那么可以称之为边双连通分量
在这里插入图片描述
如图中的CG边就是桥,删掉后G就被孤立了
A,B,C,D,E,F就是一个边双连通分量,任意删掉一条边各个点还是能互相到达
在这里插入图片描述

Tarjan法求桥

理论推导

我们选择图中任意一点为起点进行dfs,形成一颗搜索
如果w是图的割边,我们考虑w要满足什么条件:
1.w肯定在搜索树上
2.如果w连接的是x和y,并且在搜索树上x是y的父节点,
那么y无法在不经过w的前提下到达以y为根的子树外
显然,重边是影响桥的判定的。但是重边不影响割点的判定


代码实现

vec是记录了u邻边的vector,to是边的终点,no是边的编号
fano是搜索树上连接u和u父节点的边的编号
isCut[i]表示i是否是桥,注意判断桥的条件和割点有所不同

void tarjan ( int u, int fano ) {low[u] = dfn[u] = ++ cnt;int child = 0;for ( int i = 0;i < vec[u].size();i ++ ) {int v = vec[u][i].to, vno = vec[u][i].no;if ( ! dfn[v] ) {child ++;tarjan ( v, vno );if ( low[v] > dfn[u] )isCut[no] = 1;low[u] = min ( low[v], low[u] );}else if ( dfn[u] > dfn[v] && vno != fano )low[u] = min ( dfn[v], low[u] );}
} 

在这里插入图片描述

Upd:重边求桥以及连通块

void dfs1( int u, int lst = -1 ) {
//因为有重边的关系 所以不能用vector 判断是否是父亲的版本
//只能防止反向走同一边的dfn[u] = low[u] = ++ cnt; sta.push( u );for( int i = head[u];~ i;i = E[i].nxt ) {if( i == lst ) continue;int v = E[i].to;if( ! dfn[v] ) {dfs1( v, i ^ 1 );low[u] = min( low[u], low[v] );if( low[v] > dfn[u] ) {tot ++; int now;do {now = sta.top();scc[now] = tot;sta.pop();} while( now ^ v );}}else low[u] = min( low[u], dfn[v] );}
}

Tarjan法求边双连通分量

理论推导

显然,割点是存在于点双连通分量中的并且可能存在于多个点双连通分量中,
因为割点在整张图G里面是割点,但在子图G’里它就可能不再是割点,
上面给的图就是一个栗子。。。。但是桥是不可能存在于边双连通分量中的

所以只需要我们把桥从图中去掉,这样图就变成了多个连通分量,
每个连通分量就是原图的边双连通分量。
然而在实际操作中我们不需要真的去掉桥,过于麻烦,可以考虑只需要将他们标记并且在第二次dfs中不经过他们就行了

代码实现

visit[i]记录了点i是否被访问过
visitno[i]记录了编号为i的边是否被访问过
vecb[i]是记录第i个边双连通分量里边的vector

//此处省略第一次dfs求桥的代码
void dfs ( int u ) {visit[u] = 1;for ( int i = 0;i < vec[u].size();i ++ ) {int v = vec[u][i].to, vno = vec[u][i].no;if ( isCut[vno] == 0 ) {if ( visitno[vno] == 0 ) {visitno[vno] = 1;vecb[cntb].push_back ( vno );}if ( visit[v] == 0 )dfs ( v );}}
}
int main() {for ( int i = 1;i <= n;i ++ )if ( ! visit[i] ) {cntb ++;dfs ( i );}
}

在这里插入图片描述
这一篇博客主要是理论性的东西,个人觉得价值还是蛮高的,毕竟是一个新东西

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/318188.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

.NET Core下的Spring Cloud——前言和概述

前言前几年一直在写类似dubbo&#xff0c;Spring Cloud的微服务框架辗辗转转重复了多次&#xff0c;也重构推翻了很多次&#xff0c;其中诞生了“Rabbit.Rpc”,”Go”,”RabbitCloud”等开源项目。其中不乏他人对这些项目的完善。很高兴自己的开源项目能够给他人提供思路和复用…

CF785E Anton and Permutation

CF785E Anton and Permutation 题意&#xff1a; 对于一个长度为 n 的序列进行 k 次操作&#xff0c;每次操作都是交换序列中的某两个数。对于每一个操作&#xff0c;回答当前序列中有多少个逆序对。 1<n<200000 1<q<50000 题解&#xff1a; 动态逆序对&#x…

[ NOIP提高组 2016]愤怒的小鸟(暴搜 + 状压DP)// [SNOI2017]一个简单的询问(莫队)

一次性写两道题T1&#xff1a;一个简单的询问题目题解代码实现T2&#xff1a;愤怒的小鸟题目暴搜题解暴搜代码实现状压DP题解状压DP代码实现T1&#xff1a;一个简单的询问 题目 给你一个长度为 N 的序列 ai ,1≤i≤N&#xff0c;和 q 组询问&#xff0c;每组询问读入 l1,r1,l…

微软发布新的 Azure Pipelines 功能和集成

在最近举行的Connect()大会上&#xff0c;微软发布了几项新功能以及与 Azure Pipelines 的集成&#xff0c;包括 Visual Studio Code 的 Azure Pipelines 扩展、GitHub 版本管理、对 IoT 项目的支持以及 ServiceNow 集成。自从 9 月份推出 Azure Pipelines 以来&#xff0c;这种…

年末展望:Oracle 对 JDK收费和.NET Core 给我们的机遇

2018年就结束了&#xff0c;马上就要迎来2019年&#xff0c;这一年很不平凡&#xff0c;中美贸易战还在继续&#xff0c;IT互联网发生急剧变化&#xff0c;大量互联网公司开始裁员&#xff0c;微软的市值在不断上升 &#xff0c;在互联网公司的市值下跌过程中爬到了第一的位置&…

等比数列三角形 (数论 + 黄金分割点)+ JOISC 2016 Day3 T3 「电报」(基环树 + 拓扑排序)

文章目录T1&#xff1a;等比数列三角形题目题解代码实现T2&#xff1a;电报题目题解代码实现T1&#xff1a;等比数列三角形 题目 求三边都是 ≤n 的整数&#xff0c;且成等比数列的三角形个数 注意三角形面积不能为 0 注意 oeis 中未收录此数列&#xff0c;所以并不需要去搜了…

使用PerfView监测.NET程序性能(三):分组

在上一篇博客使用PerfView监测.NET程序性能&#xff08;二&#xff09;&#xff1a;Perfview的使用中&#xff0c;我们通过Perfview帮助文件中自带的代码来简单使用了Perfview&#xff0c;了解了基本操作。现在来看看Perfview中的分组操作&#xff08;Grouping&#xff09;。分…

【做题记录】构造题

CF468C Hack it! 题意&#xff1a; 令 \(F(x)\) 表示 \(x\) 的各个位上的数字之和&#xff0c;如 \(F(1234)123410\) 。 给定 \(a(a\le 10^{18})\) &#xff0c;请求出任意一组 \(l,r(l,r\le 10^{200})\) &#xff0c;要求满足&#xff1a; \[\sum_{il}^{r}F(i)\pmod{a}0 \]输出…

Star Way To Heaven (prim最小生成树) // [ NOIP提高组 2014]飞扬的小鸟(DP)

文章目录T1&#xff1a;Star Way To Heaven题目题解代码实现T2&#xff1a;飞扬的小鸟题目题解代码实现T1&#xff1a;Star Way To Heaven 题目 小 w 伤心的走上了 Star way to heaven。 到天堂的道路是一个笛卡尔坐标系上一个 n*m 的长方形通道 顶点在 (0,0) 和 (n,m) 。 小…

IdentityServer4-客户端的授权模式原理分析(三)

在学习其他应用场景前&#xff0c;需要了解几个客户端的授权模式。首先了解下本节使用的几个名词Resource Owner&#xff1a;资源拥有者&#xff0c;文中称“user”&#xff1b;Client为第三方客户端&#xff1b;Authorization server为授权服务器&#xff1b;redirection URI&…

[2019 牛客CSP-S提高组赛前集训营4题解] 复读数组(数论)+ 路径计数机(数上DP)+ 排列计数机(线段树+二项式定理)

文章目录T1&#xff1a;复读数组题目题解代码实现T2&#xff1a;路径计数机题目题解代码实现T3&#xff1a;排列计数机题目题解CODET1&#xff1a;复读数组 题目 有一个长为nk的数组&#xff0c;它是由长为n的数组A1,A2,…,An重复k次得到的。 定义这个数组的一个区间的权值为…

微软携手 Docker 打造 CNAB,分布式应用来了!

微软中国MSDN 前天Microsoft Connect(); 2018发布的众多最新科技&#xff0c;都让全球开发者惊艳不已。其中一项最令开发者瞩目并迫不及待——微软联合Docker发布了云本地应用捆绑包&#xff08;Cloud Native Application Bundle&#xff0c;以下简称CNAB&#xff09;&#xff…

[C++]试一试结构体struct node的构造函数

可直接点击跳转到构造函数处结构体概念定义结构体定义结构体及结构体变量结构体变量的特点成员调用成员函数调用结构体的构造函数Upd1Upd2Upd3结构体概念 在实际问题中&#xff0c;一组数据往往具有不同的数据类型。 例如&#xff1a;人口大普查时&#xff0c;需要记录每一个人…

[多校联考-西南大学附中]切面包(线段树/概率与期望)+ Slow Path Finding Algorithm(拓扑排序/DP)+ 分数转化(数论)

文章目录T1&#xff1a;分数转换题目题解代码实现T2&#xff1a;Slow Path Finding Algorithm题目题解代码实现T3&#xff1a;切面包题目题解代码实现T1&#xff1a;分数转换 题目 Time limit: 1.5 seconds Memory limit: 512 megabytes 给定一个十进制小数&#xff0c;请你…

P3992 [BJOI2017]开车

P3992 [BJOI2017]开车 题意&#xff1a; 题解&#xff1a; 我们要先将问题转换 圈是车&#xff0c;x是加油站。红色部分为车移动的路线 数组a是车数量的前缀和 数组b是加油站的前缀和 而a[i]与b[i]的差的绝对值就是对应的红色路被走的次数 现在车发生位置移动&#xff0c;b数…

IdentityServer4-MVC+Hybrid实现Claims授权验证(四)

上节IdentityServer4-客户端的授权模式原理分析&#xff08;三&#xff09;以对话形式&#xff0c;大概说了几种客户端授权模式的原理&#xff0c;这节重点介绍Hybrid模式在MVC下的使用。且为实现IdentityServer4从数据库获取User进行验证&#xff0c;并对Claim进行权限设置打下…

漫谈何时从单体架构迁移到微服务?

面对微服务如火如荼的发展&#xff0c;很多人都在了解&#xff0c;学习希望能在自己的项目中帮得上忙&#xff0c;当你对微服务的庐山真面目有所了解后&#xff0c;接下来就是说服自己了&#xff0c;到底如何评估微服务&#xff0c;什么时候使用微服务&#xff0c;什么时间点最…

[CSP-S Day1,Day2 游记]提高组考后总结及学习编程C++以来的心得体会

怀着沉重而感慨的心情写下了这篇blog考试中暴露的问题Day1Day2综上解决方法学习历程及以来的心得体会职业精神这篇博客我可能会写好几天&#xff0c;我jio得这篇博客对我的学习历程以及态度产生深刻影响考试中暴露的问题 首先先说这次提高组考试的每道题所遇到的各种问题吧 Da…

【.NET Core项目实战-统一认证平台】第十二章 授权篇-深入理解JWT生成及验证流程...

上篇文章介绍了基于Ids4密码授权模式&#xff0c;从使用场景、原理分析、自定义帐户体系集成完整的介绍了密码授权模式的内容&#xff0c;并最后给出了三个思考问题&#xff0c;本篇就针对第一个思考问题详细的讲解下Ids4是如何生成access_token的&#xff0c;如何验证access_t…

P5049 [NOIP2018 提高组] 旅行

P5049 [NOIP2018 提高组] 旅行 题意&#xff1a; 一棵树(可能是基环树)&#xff0c;从1出发&#xff0c;每到达一个新的点就记录下编号。求一种走法使得记录下来的编号字典序最小。 1≤n≤500000 mn−1 或 mn 题解&#xff1a; 如果不是基环树&#xff0c;那直接每次走字典…