[学习笔记]tarjan原理,割点,桥

tarjan原理

tarjan是图论中常用的算法,用于求图中的强连通分量,割点等
这里先讲强连通分量的做法

强连通分量的定义:
在一个有向图的子图中,任意两个点相互可达,也就是存在互通的路径,那么这个子图就是强连通分量(或者称为强连通分支)。如果一个有向图的任意两个点相互可达,那么这个图就称为强连通图。
tarjan基于dfs。而且每个强连通分量恰好是深搜树的一颗子树。
如果u是某个强连通分量的根,那么:
(1)u不存在路径可以返回到它的祖先
(2)u的子树也不存在路径可以返回到u的祖先。
DFN[i]标记i这个点被访问到的时间
low[i]表示i这个点直接或者间接可以到达的点里面最早被访问到的点的时间(实际上就是同一个强连通分量里的根)
步骤:
从第1个点(u)开始搜索,刚开始要把DFN[u]和low[u]赋值为被访问到的时间, 每次都去遍历这个点关联的边<u,v>:
如果v不在栈里面,继续去递归搜索v,等到回溯以后,就要对low[u]判断,如果u的子树能到达更早的点,那么就把low[u]赋值为low[v[i]]。
如果v已经在栈里面了,此时形成一个环,那么此时对于low[u]的修改就是:

这样很明显,只有强连通分量的根的DFN值和low值是相等的,其他的点都进行过修改,所以如果DFN[i]==low[i],说明我们找到了一个强连通分量的根,要得到这个强连通分量,只要把相应元素出栈就行了。
由于每个点只访问1次,每条边也是1次,所以tarjan算法的时间复杂度是O(n+m)。

代码:
#include<stdio>  
#include<cstring>  
  
const int maxn=10005;  
int DFN[maxn];//记录每个点被访问到的时间  
int low[maxn];//记录点可以直接或间接到达的最早被访问到的点(也就是那个强连通分量的根)  
int stack[maxn];  
int sccnum[maxn];//标记每个点属于第几个强连通分量  
bool instack[maxn];  
int sccNum;//强连通分量的数目  
int top;  
int index;  
int n;  
  
struct node  
{  
    int to;  
    int next;  
}edge[10*maxn];  
int head[maxn];  
int tot;  
  
void addedge(int from,int to)  
{
    edge[tot].to=to;  
    edge[tot].next=head[from];  
    head[from]=tot++;  
}  
 
void tarjan(int i)  
{  
    DFN[i]=low[i]=++index;//刚刚搜到这个点,DFN和low都赋值为被访问到的时间  
    stack[top++]=i;//入栈  
    instack[i]=1;  
    for(int j=head[i];j!=-1;j=edge[j].next)  
    {  
        if(!DFN[edge[j].to])//如果没有被访问过  
        {  
            tarjan(edge[j].to);
            //这个时候low可能要修改,值为i或者i的子树可以到达的最早被访问到的点的时间  
            if(low[i]>low[edge[j].to])  
                low[i]=low[edge[j].to];  
        }  
        else if(instack[edge[j].to])//已经在栈  
        {  
            if(low[i]>DFN[edge[j].to])  
                low[i]=DFN[edge[j].to];  
        }  
    }  
    if(DFN[i]==low[i])//找到根  
    {  
        sccNum++;  
        int v;  
        do  
       {  
            v=stack[--top];  
            sccnum[v]=sccNum;  
            instack[v]=0;//标记出栈  
        }while(v!=i);  
    }  
}  
  
void solve()  
{  
    memset(DFN,0,sizeof(DFN));  
    memset(instack,0,sizeof(instack));  
    index=0;  
    sccNum=0;  
    top=0;  
    for(int i=1;i<=n;i++)  
        if(!DFN[i])  
            tarjan(i);  
}  
  
int main()  
{  
    int m,a,b;  
    while(~scanf("%d%d",&n,&m))  
    {  
        if(n==0 && m==0)  
            break;  
        memset(head,-1,sizeof(head));  
        tot=0;  
        for(int i=0;i<m;i++)  
        {  
            scanf("%d%d",&a,&b);  
            addedge(a,b);  
        }  
        solve();  
        if(sccNum==1)  
            printf("Yes\n");  
        else  
            printf("No\n");  
    }  
    return 0;  
}



基于强联通分量,还可以扩展出一些其他的算法:
1. 割点:
定义:在一张无向图中,如果去掉某个顶点以及和这个顶点相关联的边,使得整个图的连通分支数增  加,那么这个点就是一个割点.
tarjan求割点:
若一个节点为割点,一定满足以下两个条件之一:
    1).u是dfs搜索树的根,并且u含2棵及2棵以上的子树,即不同子树的节点要想联通必须经过u,那么把u删去图中的连通分支一定增加。
    2).u不是dfs搜索树的根,并且有不等式 low[v]>=DFN[u],其中(u,v)是树枝边(即v通过u延伸开去),因为v一定不会到达时间比u小的点,否则low[v]一定会小于dfn[u],所以v要达到祖先一定要经过u。
代码:

2.桥:
    定义:
     在一张无向图中,如果去掉边(u,v)使得图的连通分支数增加,那么边(u,v)便称为桥.
     类似于割点,只不过是把点变为一条边而已。边(u,v)是无向图的桥当且仅当(u,v)满足 low[v]>DFN[u],另外要对于u,v间有重边进行特判。
代码:
void tarjan(int u,int root,int fa)    
{    
    DFN[u]=low[u]=++index;    
    instack[u]=1;    
    int cnt=0;    
    for(int i=head[u];i!=-1;i=edge[i].next)    
    {    
        int v=edge[i].to;    
        if(!instack[v])    
        {    
            tarjan(v,root,u);    
            cnt++;    
            if(low[u]>low[v])    
                low[u]=low[v];    
            if(u==root && cnt>1)    
                cut_point[u]=1;    
            else if(u!=root && low[v]>=DFN[u])    
                cut_point[u]=1;    
        }    
        else if(v!=fa && low[u] > DFN[v])    
            low[u]=DFN[v];    
    }



3.双联通分量:
在无向连通图中,如果删除该图的任何一个结点都不能改变该图的连通性,则该图为双连通的无向图。一个连通的无向图是双连通的,当且仅当它没有关键点。换言之,双连通分量里任何2个顶点之间都至少有2条不相交的路径
求法类似于求强联通分量,只不过是在无向图中。
代码:
void tarjan(int u,int fa)  
{  
    instack[u]=1;  
    DFN[u]=low[u]=++index;  
    for(int i=head[u];i!=-1;i=edge[i].next)  
    {  
        int v=edge[i].to;  
        if(fa==edge[i].id)  
            continue;  
        if(!instack[v])  
        {  
            tarjan(v,edge[i].id);  
            low[u]=min(low[u],low[v]);  
        }  
        else  
            low[u]=min(DFN[v],low[u]);  
    }  
}




  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值