【蓝桥杯】tarjan算法

一.概述

Tarjan 算法是基于DFS的算法,用于求解图的连通性问题。

Tarjan 算法可以在线性时间内求出:

无向图:

  • 割点与桥
  • 双连通分量

有向图:

  • 强连通分量
  • 必经点与必经边

 1.割点:

若从图中删除节点 x 以及所有与 x 关联的边之后,图将被分成两个或两个以上的不相连的子图,那么称 x 为图的割点

2.割边/桥:

若从图中删除边 e 之后,图将分裂成两个不相连的子图,那么称 e 为图的割边

3.搜索树:

在无向图中,我们以某一个节点 x 出发进行深度优先搜索,每一个节点只访问一次,所有被访问过的节点与边构成一棵树,我们可以称之为“无向连通图的搜索树”。

4.时间戳:​

时间戳是用来标记图中每个节点在进行深度优先搜索被访问的时间顺序。

dfn[x] 来表示。

5.追溯值:

追溯值用来表示从当前节点 x 作为搜索树的根节点出发,能够访问到的所有节点中,时间戳最小的值 —— low[x]。

二.主要思想

主要思想:

通过一次深度优先遍历,即可找出有向图中的强连通分量。

深度优先遍历有两种方式:

  • 先访问当前节点,再递归相邻节点。
  • 先递归相邻节点,再访问当前节点。

数据结构分析:

  • 我们需要给每个节点一个时间戳,这个时间戳代表了该节点被访问的次序。
  • 同时,还需要一个记录该节点通过自身/子孙能够回到的最早的时间节点。

实现步骤:

  • 我们将所有节点的时间戳初始化为0,构建递归树。
  • 循环所有节点,若dfn[i]==0,则递归访问该节点。
  • 每次递归访问节点时,我们需要将该节点压栈,给时间戳和回溯值赋初值,同时遍历该节点的相邻节点,如果相邻节点的dfn为0,则其还没有被访问过,递归访问该节点,访问结束时,更新回溯值;如果相邻节点已经在栈中,那么就直接更新回溯值。另一种情况是,该节点已经被访问过,但是从栈中弹出,那么不做任何处理。
  • 当遍历完该节点的所有邻接点,我们要判断,该节点的时间戳的回溯值是否相等,若相等,则该节点为强连通分量的根节点。开始弹栈,直到将该节点弹出栈。

三.具体应用

1.求无向图的割边/割点

无向图最最重要的点在于,不能够去处理该节点通向父节点的那条边。

这是有向图和无向图最大的区别。

有向图需要去管在栈里的节点,看能否通过栈里面的节点回到更早的时间,但是为什么要用栈,就是因为一个节点只能访问一次;而对于无向图来说,当前正在访问的节点是通过该节点的父节点访问的,而无向图是不能访问子节点通过父节点的,所以不需要栈。

1)割点的判断方法

  • 该点是根节点并且该点有两个及以上的儿子
  • 该点不是根节点并且该点有儿子并且该点儿子的追溯值大于或等于该点被访问的时间

//tarjan求无向图的割点
#include <iostream>
#include <cstring>
#include <vector>using namespace std;
const int N=100000;//链式前向星,用来表示边
struct node{int to,next;
}edge[N];
int head[N]={-1},cnt=1;//head[i]表示的是以节点i为始点的边,head[i]中的值表示的是第几条边int time_flag=0,n,m,res=0,root;
int dfn[N]={0},low[N]={0};//dfn时间戳,low追溯值
bool is_articulation[N]={false};//是否是割点//加边
void add(int u,int v){edge[cnt].to=v;edge[cnt].next=head[u];head[u]=cnt++;
}void tarjan(int u){dfn[u]=low[u]=++time_flag;int child=0;int v=head[u];while(v!=-1){int a=edge[v].to;if(dfn[a]==0){child++;tarjan(a);low[u]=min(low[u],low[a]);if((u==root&&child>1)||(u!=root&&low[a]>=dfn[u])){if(!is_articulation[u]){is_articulation[u]=true;res++;}}}else{low[u]=min(low[u],dfn[a]);}v=edge[v].next;}}int main(){ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);cin>>n>>m;//n个顶点,m条边//构建好无向图,顶点和边的下标都是从1开始memset(head,-1,sizeof(head));for(int i=0;i<m;i++){int x,y;cin>>x>>y;add(x,y);add(y,x);}for(int i=1;i<=n;i++){if(!dfn[i]){root=i;tarjan(i);}}cout<<res<<'\n';for(int i=1;i<=n;i++){if(is_articulation[i])cout<<i<<' ';}cout<<'\n';return 0;
}

 

2)割边的判断方法

若x->y这条边是割边,那么low[y]>dfn[x] 

2.求强连通分量(有向图)

//tarjan求强连通分量
#include <iostream>
#include <stack>using namespace std;
const int N=100;//链式前向星,用来表示边
struct node{int to,next;
}edge[N];
int head[N]={-1};//head[i]表示的是以节点i为始点的边,head[i]中的值表示的是第几条边int time_flag=0,n,m,cnt=0;
int dfn[N]={0},low[N]={0};//dfn时间戳,low追溯值
bool instack[N]={false};
stack<int> s;//加边
void add(int u,int v){edge[cnt].to=v;edge[cnt].next=head[u];head[u]=++cnt;
}void tarjan_dfs(int u){dfn[u]=low[u]=++time_flag;//时间戳和追溯值赋初值s.push(u);//节点入栈instack[u]=true;int v=head[u];while(v!=-1){//找与u邻接的边int a=edge[v].to;if(!dfn[a]){//a还没有被访问过tarjan_dfs(a);//深度优先访问a节点low[u]=min(low[a],low[u]);}else if(instack[a]){//v已经被访问过low[u]=min(dfn[a],low[u]);}v=edge[v].next;}if(low[u]==dfn[u]){//表明节点u是强连通分量的根int x;do{x=s.top();cout<<x<<' ';s.pop();instack[x]={false};}while(x!=u);cout<<endl;}
}
int main(){ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);cin>>n>>m;//n个顶点,m条边//构建好有向图,顶点和边的下标都是从1开始for(int i=0;i<m;i++){int x,y;cin>>x>>y;add(x, y);}for(int i=1;i<=n;i++){if(!dfn[i]){tarjan_dfs(i);}}return 0;
}

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

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

相关文章

Set和Map数据结构

Set和Map数据结构理解 Set&#xff1a; 1、es6新的数据结构&#xff0c;类似数组&#xff0c;但成员唯一 2、实例属性&#xff1a;Set.prototype.size返回Set实例的成员总数 3、操作方法&#xff1a;add、delete、has、clear 4、遍历操作&#xff1a;forEach、keys、values、en…

SpringBoot如何替换启动图标

SpringBoot项目在启动时会出现一个默认的启动图案 . ____ _ __ _ _/\\ / ____ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | _ | _| | _ \/ _ | \ \ \ \\\/ ___)| |_)| | | | | || (_| | ) ) ) ) |____| .__|_| |_|_| |_\__, | / / / /|_||___//_/_/_/::…

excel匹配替换脱敏身份证等数据

假如excel sheet1中有脱敏的身份证号码和姓名&#xff0c;如&#xff1a; sheet2中有未脱敏的数据数据 做法如下&#xff1a; 1、在sheet2的C列用公式 LEFT(A2,6)&REPT("*",8)&RIGHT(A2,4) 做出脱敏数据&#xff0c;用来与sheet1的脱敏数据匹配 2、在sheet…

图论06-飞地的数量(Java)

6.飞地的数量 题目描述 给你一个大小为 m x n 的二进制矩阵 grid &#xff0c;其中 0 表示一个海洋单元格、1 表示一个陆地单元格。 一次 移动 是指从一个陆地单元格走到另一个相邻&#xff08;上、下、左、右&#xff09;的陆地单元格或跨过 grid 的边界。 返回网格中 无法…

国内ip全局代理软件,全新的网络解决方案

在数字化高速发展的今天&#xff0c;网络已经成为我们生活、工作和学习中不可或缺的一部分。然而&#xff0c;网络环境的复杂性和多样性使得用户在使用网络时经常面临各种问题&#xff0c;如访问受限、速度缓慢等。为了解决这些问题&#xff0c;国内IP全局代理软件应运而生&…

SK海力士计划在美投资40亿美元建厂 | 百能云芯

据知情人士透露&#xff0c;韩国的SK海力士计划在美国印第安纳州西拉法叶&#xff08;West Lafayette&#xff09;投资约40亿美元兴建先进芯片封装厂。 据道琼斯社报道&#xff0c;这座工厂预计将创造约800到1,000个工作岗位&#xff0c;得益于州政府和联邦政府的减税优惠以及其…

jsonpath和json数据(序列化eval()、反序列化loads())及断言处理(断言封装)

jsonpath&#xff1a;对json串进行搜索 安装jsonpath 安装&#xff1a;pip install jsonpath 导入&#xff1a; from jsonpath import jsonpath jsonpath能通过简单的方式就能提取给定JSON中的字段。 jsonpath官方地址&#xff1a;https://goessner.net/articles/JsonPath/ 在…

matlab/simulink 火电储能一次调频,模糊控制优化储能调频系数分配,WOA鲸鱼算法优化火电储能出力占比,可模拟连续扰动,阶跃扰动

系统频率(阶跃扰动) 储能出力 储能soc对比 系统频率(连续扰动) 可见&#xff0c;鲸鱼算法woa和模糊控制储能更能有限改善频率 这里鲸鱼算法优化的是火储出力占比&#xff0c;模糊控制优化的是储能内部调频控制系数

【Java程序设计】【C00351】基于Springboot的疫情居家办公系统(有论文)

基于Springboot的疫情居家办公系统&#xff08;有论文&#xff09; 项目简介项目获取开发环境项目技术运行截图 项目简介 项目获取 &#x1f345;文末点击卡片获取源码&#x1f345; 开发环境 运行环境&#xff1a;推荐jdk1.8&#xff1b; 开发工具&#xff1a;eclipse以及i…

Kali开启远程服务

一&#xff0c;先切换root账户 二、kali开启远程服务 1&#xff0c;修改远程登录的配置文件 vim /etc/ssh/sshd_config &#xff08;用文本编辑器打开此文件) 在文件的普通模式下&#xff0c;使用/PermitRootLogin&#xff0c;回车&#xff0c;查找到该行&#xff0c;i&#…

python中的 lambda函数

lambda函数属于匿名函数&#xff0c;lambda后 冒号前面的为函数参数&#xff0c;冒号后面为函数体

什么是公网IP?

公网IP&#xff0c;即公开网络IP地址&#xff0c;是指在互联网中公开可见、可访问的IP地址。每个设备在连接互联网时&#xff0c;都需要一个唯一的公网IP地址&#xff0c;以便其他设备可以定位并与之通信。 尽管公网IP在网络通信中具有重要作用&#xff0c;但它也带来了一些安全…