Tarjan
无向图求割边
inline void Tarjan(int u,int fa){
dfn[u]=low[u]=++Time;
for(int i=head[u];i!=-1;i=e[i].next){
int v=e[i].to;
if(!dfn[v]){
Tarjan(v,u);
low[u]=min(low[v],low[u]);
if(low[v]>dfn[u]){
bridge[i]=bridge[i^1]=1;//边的编号从零开始(显然不满足1^1=2,所以1的反边应该是0)
}
}
else if(v!=fa){// 无向图中不存在重边时,根据定义不能用dfn[fa]更新low[u]
low[u]=min(low[u],dfn[v]);
}
}
}
边的编号从零开始(显然不满足1^1=2,所以1的反边应该是0)
《进阶指南上》边编号从2开始(一上午都没有看到,甚至一直在质疑作者的权威)
update2022 01 23
有向图求强连通分量(缩点)
void Tarjan(int u){
dfn[u]=low[u]=++Time;vis[u]=1;s.push(u);
for(int i=head[u];i;i=e[i].next){
int v=e[i].to;
if(!dfn[v]){
Tarjan(v);
low[u]=min(low[v],low[u]);
}
else if(vis[v])low[u]=min(low[u],dfn[v]);
}
if(dfn[u]==low[u]){
tot++;
while(s.top()!=u){
int v=s.top();s.pop();
vis[v]=0;
belong[v]=tot;
cnt[tot]++;
}
vis[u]=0;belong[u]=tot;cnt[tot]++;s.pop();//u出栈
}
}
老姚的代码
Tarjan老姚
void tarjan(int u){
dfn[u] = low[u] = ++Time;//初始化
vis[u] = 1;//u为灰色,正在访问中
s[++top]=u;//进栈
for (int i = heade[u]; i; i = e[i].next){
int v = e[i].to;
if (!dfn[v]) {//v为白点
tarjan(v);
low[u] = std::min(low[u], low[v]);//<u,v>为树枝子节点low值更新父节点low值
}//否则有可能是返祖边
else if (vis[v] == 1) low[u] = std::min(low[u], dfn[v]);//v 为灰色,表明 <u,>为返祖边,有环
}
vis[u] = -1;//u子树访问结束,变为黑点
if(dfn[u] == low[u]){//缩点
++tot;//记录新图节点数
while(s[top+1]!=u){//从栈顶到u的点缩成一个新点tot
int v=s[top];
belong[v]=tot;sum[tot]+=a[s[top--]];
}//belong表示强连通分量编号,vis表示是否在栈中,sum表示强连通分量权值和
}
}
5_Lei看了老姚的博客,学习了Tarjan,模板wa60。关于为什么vis[u] = -1不可以while循环外面,我们进行了深入探讨。感谢Chen_jr大佬的指点和hack
vis数组用来标记是否在栈里,从这个角度来看,u还没有出栈如果没有进入while循环就把vis清零显然是不对的但是这个解释我不接受
hack:有向环套有向环(1,2,3)(1,4,3,5)
根据定义,low值是指节点u能通过不在搜索树上的点访问到的最早的点的时间戳,并且该点在栈中。由此不在栈中的点不能更新u,在栈中且u能访问到的点一定要更新u。
如果一个u访问到的点v不在栈里,并且dfn[v]不为零,只有一种情况,v在一个环里,并且已经执行了缩点的操作,如果用v的dfn更新u,会造成dfn[u]!=low[u]的情况,如果u是一个孤立点那么新图中就不会有这个点。
(忽略这个六)
我们用老姚的代码来模一下这个图。
1 dfn[1]=1 ,low[1]=1 ,vis[1]=1
2 dfn[2]=2 ,low[2]=2 ,vis[2]=1
3 dfn[3]=3 ,low[3]=3 ,vis[3]=1
low[3]=min(dfn[1],low[3])=1
4 回溯 vis[3]=-1
5 回溯 low[2]=min(low[3],low[2])=1,
vis[2]=-1
6 dfn[4]=4 ,low[4]=4 ,vis[4]=1
7 dfn[5]=5 ,low[5]=5 ,vis[5]=1
8 vis[3]==-1不能更新
stack[5,4,3,2,1]
9 回溯vis[5]=-1
dfn[5]==low[5]=5,5出栈
10回溯vis[4]=-1
dfn[4]==low[4]=4,4出栈
11回溯vis[1]=-1
dfn[1]==low[1]=1, 3,2,1出栈
结束
由于5没有被更新,所以5没有被放到环里,而是作为一个孤立点存到了新图中,4同理。老姚vis更新早了,应该在出栈的过程中vis归零
Tarjan求点双&边双
在一个无向图中,若任意两点间至少存在两条“点不重复”的路径,则说这个图是点双连通的(简称双连通,biconnected)
在一个无向图中,点双连通的极大子图称为点双连通分量(简称双连通分量,Biconnected Component,BCC)
inline void Tarjan(int u,int fa){//点双
dfn[u]=low[u]=++Time;s.push(u);
int flag=0;
for(int i=head[u];i;i=e[i].next){
int v=e[i].to;
if(!dfn[v]){
Tarjan(v,u);
if(low[v]>=dfn[u]){
flag++;
if(u!=root || flag>1)cut[u]=1;
tot++;int x=0;
do{
x=s.top();s.pop();
dcc[tot].push_back(x);
}while(x!=v);
dcc[tot].push_back(u);
}
low[u]=min(low[v],low[u]);
}
else if(v!=fa){//无向图中不存在重边时,根据定义不能用dfn[fa]更新low[u]
low[u]=min(low[u],dfn[v]);
}
}
}
Tarjan求边双
先跑一遍求出所有桥,dfs遇到桥就跳过
void dfs(int u){
belong_dcc[u]=dcc;
for(int i=head[u];i!=-1;i=e[i].next){
int v=e[i].to;
if(bridge[i]==1||belong_dcc[v]!=0)continue;
dfs(v);
}
}
for(int i=1;i<=n;++i){
if(!belong_dcc[i]){
dcc++;dfs(i);
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了