zoukankan      html  css  js  c++  java
  • Tarjan算法 (强联通分量 割点 割边)

    变量解释:
    low 指当前节点在同一强连通分量(或环)能回溯到的dfn最小的节点
    dfn 指当前节点是第几个被搜到的节点(时间戳)
    sta 栈
    vis 是否在栈中
    ans 指强连通分量的数量
    top 栈顶

    1.求强连通分量
    定义:如果两个顶点可以相互通达,则称两个顶点强连通(strongly connected)。如果有向图G的每两个顶点都强连通,称G是一个强连通图。有向图的极大强连通子图,称为强连通分量(strongly connected components)。

    算法:在有向图中从一点(u)开始dfs,记录dfn,搜到一个已在栈中的点(v)时用dfn[v] (low[v]也行,但只有求强连通分量时可以别的只能用dfn[v]) 尝试更新low[u],并在回溯时更新沿路的点的low值,走到low值与dfn相同的点时记录这个强连通分量即可。
    也就是说:在同一个强连通分量中所有点low值相同,也就是有一个代表点(代表点即所有点的low值即强连通分量中dfn值最小的点)
    时间复杂度为O(E+V)

    code

    void tarjan(int u){
        dfn[u]=low[u]=++cnt;//初始化一点的dfn和low
        sta[++top]=u,vis[u]=true;//入栈
        for(int i=head[u];i;i=edge[i].next){//邻接表
            int v=edge[i].to;
            if(!dfn[v]){//如果没走过
                tarjan(v);
                low[u]=min(low[u],low[v]);//回溯过程时low值传递
            }
            else if(vis[v]) low[u]=min(low[u],dfn[v]); //low[v]也行 用代表点更新
        }
        if(dfn[u]==low[u]) {//如果是代表点 记录并出栈
            ans++;//记录强连通分量个数
            while(sta[top]!=u){
                vis[sta[top]]=false;
                top--;
            }
            vis[sta[top]]=false;
            top--;
        }
        return ;
    }

    2.求无向图的割点与割边
    割点:在无向图中,如果将一个点以及所有连接该点的边都去掉,图就不再连通,那么这个点就叫做这个图的一个割点。
    割边:在无向图中,如果将一条边去掉,图就不再连通则称这条边为图的一个割边。

    求割点:如果一个点(u)所连接的几个节点(v)的low值大或等于此节点(u)的dfn值时说明之后的节点(v)无法连接到比此点(u)更早的点上,则说明这个节点(u)是一个割点。PS:根节点需特判,当根节点在dfs树有两个或更多个子树时则说明根节点是割点

    求割边:与割点类似,如果一个点(u)的dfn值大于(不能等于,否则不一定)和它连接的一个节点(v)的low值,则说明这条边(uv)为图的一个割边

    变量解释:
    sum 指总共有几个割点(边)

    割点code

    void cutpoint(int u){
        int fl=0;//为特判准备
        dfn[u]=low[u]=++cnt;//初始化
        for(int i=head[u];i;i=edge[i].next){//用邻接表,下同
            int v=edge[i].to;
            if(!dfn[v]){
                cutpoint(v);
                low[u]=min(low[u],low[v]);
                if(u!=root&&low[v]>=dfn[u]&&!cpoint[u]) sum++,cpoint[u]=1;//不是根节点&&v的low值>=u的dfn值&&此点没有算过
                if(u==root) fl++;//此时特判++
            }
            low[u]=min(low[u],dfn[v]); 
        }
        if(fl>=2&&!cpoint[u]) sum++,cpoint[u]=1;//根节点若有两棵子树则是割点
    }

    割边code

    void cutedge(int u,int f){
        dfn[u]=low[u]=++cnt;
        for(int i=head[u];i;i=edge[i].next){
            int v=edge[i].to;
            if(!dfn[v]){
                cutedge(v,u);
                low[u]=min(low[u],low[v]);
                if(dfn[u]<low[v]) cedge[++sum]=i;//记录边的序号
            }
            else if(v!=f) low[u]=min(low[u],dfn[v]); //只有当v不是u的上一个节点时可行
        }
    }

    完整模板code:
    ps:这里就不打注释了,核心就在上面的部分里

    #include<cstdio>
    #include<iostream>
    #include<cstring>
    using namespace std;
    
    const int MAX=1000010;
    int n,m,cnt,sum,root;
    int head[MAX],low[MAX],dfn[MAX],cpoint[MAX],cedge[MAX];
    
    struct edg{
        int to,next,from;
    }edge[MAX];
    
    void add(int x,int y){
        edge[++cnt].next=head[x];
        edge[cnt].from=x,edge[cnt].to=y;
        head[x]=cnt;
    }
    
    void cutpoint(int u){
        int fl=0;
        dfn[u]=low[u]=++cnt;
        for(int i=head[u];i;i=edge[i].next){
            int v=edge[i].to;
            if(!dfn[v]){
                cutpoint(v);
                low[u]=min(low[u],low[v]);
                if(u!=root&&low[v]>=dfn[u]&&!cpoint[u]) sum++,cpoint[u]=1;
                if(u==root) fl++;
            }
            low[u]=min(low[u],dfn[v]); 
        }
        if(fl>=2&&!cpoint[u]) sum++,cpoint[u]=1;
    }
    
    void cutedge(int u,int f){
        dfn[u]=low[u]=++cnt;
        for(int i=head[u];i;i=edge[i].next){
            int v=edge[i].to;
            if(!dfn[v]){
                cutedge(v,u);
                low[u]=min(low[u],low[v]);
                if(dfn[u]<low[v]) cedge[++sum]=i;
            }
            else if(v!=f) low[u]=min(low[u],dfn[v]);
        }
    }
    
    void mset(){
        memset(dfn,0,sizeof dfn);
        memset(low,0,sizeof low);
        cnt=sum=0;
    }
    
    void find_cutpoint(){
        for(int i=1;i<=n;i++) if(!dfn[i]) {
            root=i;
            cutpoint(i);
        }
        printf("%d
    ",sum);
        for(int i=1;i<=n;i++) if(cpoint[i]) printf("%d ",i);
    }
    
    void find_cutedge(){
        for(int i=1;i<=n;i++) if(!dfn[i]) cutedge(i,0);
        printf("%d
    ",sum);
        for(int i=1;i<=sum;i++) printf("%d %d
    ",edge[cedge[i]].from,edge[cedge[i]].to); 
    }
    
    int main(){
    //  freopen("testdata.txt","r",stdin);
        scanf("%d %d",&n,&m);
        for(int i=1;i<=m;i++){
            int a,b;
            scanf("%d %d",&a,&b);
            add(a,b);
            add(b,a);
        }
        find_cutedge();
        mset();
        find_cutpoint();
        return 0;
    }
    版权声明:本文为博主原创文章,未经博主允许不得转载。 博主:https://www.cnblogs.com/Menteur-Hxy/
  • 相关阅读:
    木马后门入侵与RKHunter,ClamAV检测工具
    Jenkins环境搭建
    Mha-Atlas-MySQL高可用
    JAVA企业级应用服务器之TOMCAT实战
    Keepalived高可用集群
    scp ssh-key连接原理
    jumpserver跳板机搭建
    DNS域名解析服务器
    DHCP服务
    Keepalived高可用集群
  • 原文地址:https://www.cnblogs.com/Menteur-Hxy/p/9248051.html
Copyright © 2011-2022 走看看