详解tarjan求强联通分量

tarjan算法是在有向图中求强联通分量的一种算法,基于dfs

其中最重要的需要维护的两个数组是low[maxn],dfn[maxn]

low[u]代表u可以到达的深度最低的节点的深度值,dfn[u]代表u在dfs树中的深度

其原理是

在深度优先搜索树中,同属于一个强联通分量的点一定在同一颗子树下

由此则有:

1.若一个点u是强联通分量的根节点,那么这个点在dfs中的遍历顺序dfn[u]的大小一定会等于它可以到达的最小节点的遍历顺序low[u],即这个点最短只能自己到自己
2.若一个点u不是强联通分量的根节点,那么它可以到达的最小节点的遍历顺序low[u]一定小于他自身的遍历顺序大小dfn[u]

换句话说,在dfs到一个强联通分量时,第一个被dfs到的点的dfs序一定比这个强联通分量里的其他点的dfs序小,于是我们就可以把这个强连通分量看作一棵树,用染色的方式来将它们全部找出来

注意两个概念:

1.这里讲到的遍历顺序是指用dfs以某个确定节点为起点遍历整个图时访问到某个节点的顺序,如根节点的dfn为1,根节点下一层的节点的dfn都为2

2.上面提到的近是用dfn来衡量的,即dfn越小,点越近




好了,明白了原理之后,算法框架就可以构建了

假设要找点u属于的强联通分量中有哪些点

1.管你怎么办,反正首先存一个图

2.然后以u为起点,将u放入一个栈中,标记u在栈中

3.对于u的每一个子节点v构成的边(u,v),都有三种情况:要么是一条树边,要么是一条返祖边,要么是一条横叉边,故我们可以对树边和返祖边进行处理,而横叉边不用考虑(若在同一个分量中就不可能构成横叉边了),于是对于树边(特征是没有被访问过),就先dfs再用low[u]和low[v]的较小值来更新low[u],对于返祖边(特征是在栈中),就用low[u]和dfn[v]的较小值来更新low[u]

4.如果u是根节点,即dfn[u]==low[u],那就意味着栈中u上面的节点都是u的联通点,于是将u上面的点都弹出来放在数组中就可以得到u的强连通分量点了

相关代码如下

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#define MAX 100010
int dfsnum[MAX],dfsNum,low[MAX];
int sccnum[MAX],sccNum;
int instack[MAX],st[MAX],top;
typedef struct EDGE
{
    int v,next;
}edge;
edge e[MAX];
int edgeNum;
int head[MAX];
void insertEdge(int a,int b)
{
    e[edgeNum].v=b;
    e[edgeNum].next=head[a];
    head[a]=edgeNum++;
}
void Tarjan(int i)//找i的强联通分量 
{
    dfsnum[i]=low[i]=++dfsNum;//时间戳,最近访问节点为dfsNum 
    st[top++]=i;//入栈 
    instack[i]=1;//标记入栈 
    int j=head[i];
    for(j=head[i];j!=-1;j=e[j].next)//遍历i的子节点 
    {
        int v=e[j].v;//v是i的子节点 
        if(dfsnum[v]==0)//为树边,因为v没有经过dfs 
        {
            Tarjan(v);//dfs v 
            if(low[i]>low[v])//其实就是更新low值,最近访问节点值 
                low[i]=low[v];//可改成low[i]=min(low[i],low[v]); 
        }
        else if(instack[v])//如果是反祖边的话,就意味着i的low值可能会变小 
        {
            if(low[i]>dfsnum[v])
                low[i]=dfsnum[v];//low[i]=min(low[i],dfsnum[v]); 
        }
    }
    if(dfsnum[i]==low[i])//如果i是根节点,马上吐栈 
    {
        do
        {
            top--;
            sccnum[st[top]]=sccNum;
            instack[st[top]]=0;
        }while(top>=0&&st[top]!=i);
        sccNum++;
    }
}
void solve(int n)
{
    int i;
    memset(dfsnum,0,sizeof(dfsnum));
    memset(instack,0,sizeof(instack));
    dfsNum=0;
    top=0;
    sccNum=0;
    for(i=1;i<=n;i++)
    {
        if(dfsnum[i]==0)//如果没有进入分量图,tarjan(i),找i的强联通分量 
            Tarjan(i);
    }
}

HAOI2006 famous cows

#include<bits/stdc++.h>
using namespace std;
#define loop(i,start,end) for(int i=start;i<=end;++i)
#define anti_loop(i,start,end) for(register int i=start;i>=end;--i)
#define max(a,b) ((a>b)?a:b)
#define min(a,b) ((a<b)?a:b)
#define ll long long
#define clean(arry,num) memset(arry,num,sizeof(arry))
template<typename T>void read(T &x){
	x=0;char r=getchar();T neg=1;
	while(r>'9'||r<'0'){if(r=='-')neg=-1;r=getchar();}
	while(r>='0'&&r<='9'){x=(x<<3)+(x<<1)+r-'0';r=getchar();}
	x*=neg;
}
int n,m,cnt=0;
const int maxn=10000+10,maxm=50000+10;
struct node{int v;int nxt;}edge[maxm];
int head[maxn],conp=0;
int dfn[maxn],low[maxn];
int st[maxn],top=0;
int belong[maxn],numofB[maxn],cont=0;
int outcome[maxn];
inline void addl(int u,int v){
    edge[cnt].v=v;
    edge[cnt].nxt=head[u];
    head[u]=cnt++;
}
void tarjan(int u){
    dfn[u]=low[u]=++conp;
    st[++top]=u;
    for(int i=head[u];i!=-1;i=edge[i].nxt){
        int v=edge[i].v;
        if(!dfn[v]){
            tarjan(v);
            low[u]=min(low[v],low[u]);
        }
        else if(!belong[v])low[u]=min(low[u],dfn[v]);
    }
    if(dfn[u]==low[u]){
        belong[u]=++cont;
		++numofB[cont];
        while(top>=0&&st[top]!=u){
            belong[st[top]]=cont;
            ++numofB[cont];
            --top;
        }
        --top;
    }
}
int main(){
    #ifndef ONLINE_JUDGE
    freopen("datain.txt","r",stdin);
    #endif // ONLINE_JUDGE
    clean(head,-1);clean(dfn,0),clean(low,0);
    read(n),read(m);clean(outcome,0);clean(numofB,0);
    loop(_i,1,m){
		int xi;int yi;
        read(xi),read(yi);
        addl(xi,yi);
    }
    loop(i,1,n)
        if(!dfn[i])
            tarjan(i);
    loop(p,1,n){
        for(int i=head[p];i!=-1;i=edge[i].nxt){
            if(belong[edge[i].v]!=belong[p])
                ++outcome[belong[p]];
        }
    }
    int res=0;
    loop(i,1,cont)
        if(outcome[i]==0){
            if(res){
                res=0;break;
            }
            res+=numofB[i];
        }
    printf("%d\n",res);
    return 0;
}

tarjan这个人很厉害,他发明了3个算法,分别可以求强联通分量,桥和割点

断点

若点u是断点,显然它后面的点的low数组都比割点的low数组大

若low[v]>dfn[u],则(u,v)为割边。但是实际处理时我们并不这样判断,因为有的图上可能有重边,这样不好处理。我们记录每条边的标号(一条无向边拆成的两条有向边标号相同),记录每个点的父亲到它的边的标号,如果边(u,v)是v的父亲边,就不能用dfn[u]更新low[v]。这样如果遍历完v的所有子节点后,发现low[v]=dfn[v],说明u的父亲边(u,v)为割边。

给道题吧:


天凯是SU的总书记。SU有n个城市,某些城市之间修筑了公路。任意两个城市都可以通过公路直接或者间接到达。

天凯发现有些公路被毁坏之后会造成某两个城市之间无法互相通过公路到达。这样的公路就被称为dangerous pavement。

为了防止AM帝国对dangerous pavement进行轰炸,造成某些城市的地面运输中断,天凯决定在所有的dangerous pavement驻扎重兵。可是到底哪些是dangerous pavement呢?你的任务就是找出所有这样的公路。

Input format:

第一行n,m(1<=n<=150, 1<=m<=5000),分别表示有n个城市,总共m条公路。

以下m行每行两个整数a, b,表示城市a和城市b之间修筑了直接的公路。

Output format:

输出有若干行。每行包含两个数字a,b(a<b),表示<a,b>是dangerous pavement。请注意:输出时,所有的数对<a,b>必须按照a从小到大排序输出;如果a相同,则根据b从小到大排序。

Sample:

Danger.in

6 6

1 2

2 3

2 4

3 5

4 5

5 6

Dager.out

1 2

5 6


code:

#include<bits/stdc++.h>
using namespace std;
const int maxn=150+5;
const int maxm=5000+5;
bool G[maxn][maxn];
int low[maxn],dfn[maxn],DFN=0;
int n,m;
int numofgroup=0,pointer=0;
struct node{    
	int u;    
	int v;
};
node ans[maxm];
void datasetting()
{    
	scanf("%d%d",&n,&m);    
    for(int i=0;i<n;i++)memset(G[i],0,sizeof(G[i]));
    int u,v;    
    for(int i=0;i<m;i++)    
    {        
    	scanf("%d%d",&u,&v);        
    	G[u][v]=G[v][u]=true;    
    }
}
void tarjan(int u,int fa)
{    
	low[u]=dfn[u]=++DFN;    
    for(int v=1;v<=n;v++)    
    {        
    	if(G[u][v])        
    	{            
    		if(!dfn[v])            
            {                
            	tarjan(v,u);                
            	low[u]=min(low[v],low[u]);           
                if(dfn[u]<low[v])                
            		{                    
            			node a;                    
                		a.u=u;a.v=v;                 
                        ans[pointer++]=a;                
            		}            
            }            
            else if(v!=fa)low[u]=min(low[u],dfn[v]);       	 }             
     }
}
bool cmp(node a,node b)
{    
	if(a.u==b.u)return a.v<b.v;    
    return a.u<b.u; 
}
int main()
{ 
	datasetting();    
    for(int i=1;i<=n;i++)if(!dfn[i])tarjan(i,i);    
    sort(ans,ans+pointer,cmp);    
    for(int i=0;i<pointer;i++)    
    {        
    	node a=ans[i];
        printf("%d %d\n",a.u,a.v);
    }    
	return 0;
} 
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AndrewMe8211

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值