Tarjan(年早失修 漏洞百出)
Tarjan是谁
Tarjan's SCCs(有向图强连通分量)algorithm
给定⼀个有向图 ,若存在 ,满⾜从 出发能到达 中的所有的点,则称 是⼀个源点为 流图。
从 出发做 。
符号表
-
: 节点的父亲节点
-
: 节点的祖先点集
-
:搜索树中 节点的儿子节点集
-
:
-
: 节点的时间戳
-
: 节点为根的子树点集
-
: 节点的追溯值
中的每条有向边 必然是以下四种之⼀:
-
枝:
-
前:
-
后:
-
横: 此时一定满足
节点上的数字为时间戳:
分析
我们在搜索树上分析,发现“前”边没有什么用处,因为搜索树上本来就存在从 到 的路径。“后”边非常有用,它可以和搜索树上从 到 的路径⼀起构成环。“横”边要看情况,如果从 出发能找到⼀条路径回到 的祖先节点,那么 就是有用的。
定义为满⾜以下条件的节点的最小时间戳:
-
该点在栈中。
-
存在⼀条从 出发的有向边,以该点为终点。
主体
注意标红和标蓝的不能改,不能错!!!
Code
vector<int> e[N]; int dfn[N];//时间戳 int low[N];//追溯值 int tim=0;//时间戳计数器 int col[N];//所属 SCC (为其中一个点的 id) int st[N];//stack int tot=0;//stack_top bool in[N];//是(1)否(0)在栈中 void tar(int x){ dfn[x]=low[x]=++tim;//init st[++tot]=x;//进栈 in[x]=1; for(int i:e[x]){ if(!dfn[i]){//萌新 tar(i);//递归 ckmn(low[x],low[i]); }else if(in[i]){//祖先 ckmn(low[x],dfn[i]); } } if(low[x]==dfn[x]){//导出 SCC do{ col[st[tot]]=x; //着上 x 的颜色,以后你就是 x 的人了 in[st[tot]]=0; }while(st[tot--]!=x);//pop until x popped } }
Tarjan's BCCs(无向图双连通分量)algorithm
与 SCC 类似,所以符号沿用
注意此时 的定义改变,且图 中不再存在意义上的“横”边、“前”边。
定义为满⾜以下条件之一的节点的最小时间戳:
-
该点在 中。
-
存在⼀条从该点出发至 中任一点的非树边。
剩下的分析和算法就一样了~
桥
桥⼀定是搜索树上的边。
Code by FuZhenTao
const int SIZE=100010; int head[SIZE],ver[SIZE*2],nxt[SIZE*2]; int dfn[SIZE],low[SIZE],n,m,tot,num; bool bridge[SIZE*2]; void add(int x,int y){ ver[++tot]=y; nxt[tot]=head[x]; head[x]=tot; } void tarjan(int x,int in_edge){ dfn[x]=low[x]=++num; for(int i=head[x];i;i=nxt[i]){ int y=ver[i]; if(!dfn[y]){ tarjan(y,i); low[x]=min(low[x],low[y]); if(low[y]>dfn[x]) bridge[i]=bridge[i^1]=true; } else if(i!=(in_edge^1)){ low[x]=min(low[x],dfn[y]); } } } int main(){ cin>>n>>m; tot=1; for(int i=1;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]) tarjan(i,0); } for(int i=2;i<tot;i+=2){ if(bridge[i]) cout<<ver[i^1]<<" "<<ver[i]<<endl; } }
割点
Code by FuZhenTao
void tarjan(int x){ dfn[x]=low[x]=++num; int flag=0; for(int i=head[x];i;i=nxt[i]){ int y=ver[i]; if(!dfn[y]){ tarjan(y); low[x]=min(low[x],low[y]); if(low[y]>=dfn[x]){ flag++; if(x!=root||flag>1) cut[x]=true; } } else low[x]=min(low[x],dfn[y]); } }
e-DCC(边双连通分量)
设 是无向连通图。
只需要求出无向图中所有的桥,把桥都删除之后,图会分成若干个连通块,每个连通块就是⼀个"边双连通分量"。
Code by FuZhenTao
int c[SIZE],dcc; void dfs(int x){ c[x]=dcc; for(int i=head[x];i;i=nxt[i]){ int y=ver[i]; if(c[y]||bridge[i]) continue; dfs(y); } } for(int i=1;i<=n;i++){ if(!c[i]){ ++dcc; dfs(i); } }
v-DCC(点双连通分量)
v-DCC 的求法炒鸡麻烦,鸽了。
由于 Tarjan 求 LCA 好像并不是 Tarjan 的算法,而且倍增 好用并好写,所以就不再论述了。
本文来自博客园,作者:ShaoJia,版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义