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);
        }
    }

posted @   Chano_sb  阅读(39)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示