图论 —— 图的连通性 —— Tarjan 求双连通分量

【概念】

1.双连通分量:对于一个无向图,其边/点连通度大于1,满足任意两点之间,能通过两条或两条以上没有任何重复边的路到达的图,即删掉任意边/点后,图仍是连通的

2.分类:

    1)点双连通图:点连通度大于 1 的图

    2)边双连通图:边连通度大于 1 的图

【原理】

1.求点双连通分量

求点双连通分量可以在求割点的同时用栈维护。

在搜索图时,每找到一条树枝边或后向边(非横叉边),就把这条边加入栈中。如果遇到满足 dfn(u)<=low(v),说明 u 是一个割点,同时把边从栈顶一个个取出,直到遇到了边 (u,v),取出的这些边与其关联的点,就组成一个点双连通分支。

割点可以属于多个点双连通分支,其余点和每条边只属于且属于一个点双连通分支。

2.求边双连通分量

求边双连通分量时,需要先求出桥,然后把桥全部去掉,原图变成多个连通块,此时每个连通块就是一个边双连通分量。

桥不属于任何一个边双连通分量,其余的边和每个顶点都属于且只属于一个边双连通分量

【过程】

1.求点双连通分量

1)Tarjan 求割点

2)每找到一个割点,将它上面的所有点弹出栈,所得到的点集就是点双连通分量

2.求边双连通分量

1)Tarjan 找桥

2)删除桥

3)剩余各部分即为边双连通分量

【实现】

1.求点双连通分量

int n,m;
vector<int> G[N];
vector<int> bcc[N];//包含i号点双连通分量的所有结点
int dfn[N];
bool iscut[N];//记录i结点是否是割点
int bccno[N];//记录第i个点属于第几号点双连通分量
int block_cnt;//时间戳
int bcc_cnt;//记录点双连通分量个数
struct Edge {
    int x;
    int y;
};
stack<Edge> S;
int Tarjan(int x,int father) {
    int lowx=dfn[x]=++block_cnt;
    int child=0;//子节点数目
    for(int i=0; i<G[x].size(); i++) {
        int y=G[x][i];//当前结点的下一结点
        Edge e;
        e.x=x;
        e.y=y;
        if(dfn[y]==0) { //若未被访问过
            S.push(e);
            child++;//未访问过的节点才能算是x的孩子

            int lowy=Tarjan(y,x);
            lowx=min(lowx,lowy);
            if(lowy>=dfn[x]) {
                iscut[x]=true;//x点是割点
                bcc_cnt++;
                bcc[bcc_cnt].clear();

                while(true) {
                    Edge temp=S.top();
                    S.pop();
                    if(bccno[temp.x]!=bcc_cnt) {
                        bcc[bcc_cnt].push_back(temp.x);
                        bccno[temp.x]=bcc_cnt;
                    }
                    if(bccno[temp.y]!=bcc_cnt) {
                        bcc[bcc_cnt].push_back(temp.y);
                        bccno[temp.y]=bcc_cnt;
                    }
                    if(temp.x==x && temp.y==y)
                        break;
                }
            }
        } else if(dfn[y]<dfn[x] && y!=father) { //y!=father确保了(x,y)是从x到y的反向边
            S.push(e);
            lowx=min(lowx,dfn[y]);
        }
    }
    if(father<0 && child==1 )//x若是根且孩子数<=1,那x就不是割点
        iscut[x]=false;
    return lowx;
}
int main() {
    scanf("%d%d",&n,&m);
    for(int i=0; i<n; i++)
        G[i].clear();
    while(m--) {
        int x,y;
        scanf("%d%d",&x,&y);
        G[x].push_back(y);
        G[y].push_back(x);
    }

    bcc_cnt=0;
    block_cnt=0;
    memset(dfn,0,sizeof(dfn));
    memset(iscut,0,sizeof(iscut));
    memset(bccno,0,sizeof(bccno));
    for(int i=0; i<n; i++)
        if(!dfn[i])
            Tarjan(i,-1);

    printf("点-双连通分量一共%d个\n",bcc_cnt);
    for(int i=1; i<=bcc_cnt; i++){
        printf("第%d个点-双连通分量包含以下点:\n",i);
        sort(&bcc[i][0],&bcc[i][0]+bcc[i].size()); //对vector排序,使输出的点从小到大
        for(int j=0; j<bcc[i].size(); j++)
            printf("%d ",bcc[i][j]);
        printf("\n");
    }
    return 0;
}

2.求边双连通分量

struct Edge {
    int x;
    int yl
} edge[N];
int n,m;
int dfn[N],low[N];
int bccno[N];
vector<int> G[N],bcc[N];
int sig,block_cnt;
bool g[N][N],isbridge[N];
void tarjan(int x,int father) {
    dfn[x]=low[x]=++block_cnt;
    for(int i=0; i<G[x].size(); i++) {
        int y=edge[G[x][i]].y;
        if(!dfn[y]) {
            tarjan(y,x);
            low[x]=min(low[x],low[y]);
            if(low[y]>dfn[x]) {
                isbridge[G[x][i]]=1;
                isbridge[G[x][i]^1]=1;
            }
        } else if(dfn[y]<dfn[x] && y!=father)
            low[x]=min(low[x], dfn[y]);
    }
}
void dfs(int x) {
    dfn[x]=1;
    bccno[x]=sig;
    for(int i=0; i<G[x].size(); i++) {
        int y=G[x][i];
        if(isbridge[y])
            continue;
        if(!dfn[edge[y].y])
            dfs(edge[y].y);
    }
}
int main() {
    scanf("%d%d",&n,&m);
    for(int i=0; i<n; i++)
        G[i].clear();
    while(m--) {
        int x,y;
        scanf("%d%d",&x,&y);
        G[x].push_back(y);
    }

    sig=0;
    block_cnt=0;
    memset(dfn,0,sizeof(dfn));
    memset(low,0,sizeof(low));
    memset(isbridge,0,sizeof(isbridge));
    memset(bccno,0,sizeof(bccno));
    memset(bcc,0,sizeof(bcc));

    for(int i=1; i<=n; i++) //先DFS找桥
        if(!dfn[i])
            tarjan(i, -1);

    memset(dfn,0,sizeof(dfn));
    for(int i=1; i<=n; i++){ //再DFS找边双连通分量
        if(!dfn[i]) {
            sig++;
            dfs(i);
        }
    }
    printf("%d\n",sig);//边双连通分量个数
    return 0;
}

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值