0
点赞
收藏
分享

微信扫一扫

tarjan算法

weipeng2k 2022-02-11 阅读 82
算法图论

文章目录

tarjan算法介绍

如果两个顶点可以相互通达,则称两个顶点强连通(strongly connected)。如果有向图G的每两个顶点都强连通,称G是一个强连通图。有向图的极大强连通子图,称为强连通分量(strongly connected components)。
图1中,子图{1,2,3,4}为一个强连通分量,因为顶点1,2,3,4两两可达。{5},{6}也分别是两个强连通分量。
Tarjan算法是用来求有向图的强连通分量的。求有向图的强连通分量的Tarjan算法是以其发明者Robert Tarjan命名的。Robert Tarjan还发明了求双连通分量的Tarjan算法。
Tarjan算法是基于对图深度优先搜索的算法,每个强连通分量为搜索树中的一棵子树。搜索时,把当前搜索树中未处理的节点加入一个堆栈,回溯时可以判断栈顶到栈中的节点是否为一个强连通分量。
定义DFN(u)为节点u搜索的次序编号(时间戳),Low(u)为u或u的子树能够追溯到的最早的栈中节点的次序号。
当DFN(u)=Low(u)时,以u为根的搜索子树上所有节点是一个强连通分量。
在这里插入图片描述
算法演示:
从节点1开始DFS,把遍历到的节点加入栈中。搜索到节点u=6时,DFN[6]=LOW[6],找到了一个强连通分量。退栈到u=v为止,{6}为一个强连通分量。

返回节点5,发现DFN[5]=LOW[5],退栈后{5}为一个强连通分量。

返回节点3,继续搜索到节点4,把4加入堆栈。发现节点4向节点1有后向边,节点1还在栈中,所以LOW[4]=1。节点6已经出栈,(4,6)是横叉边,返回3,(3,4)为树枝边,所以LOW[3]=LOW[4]=1。

继续回到节点1,最后访问节点2。访问边(2,4),4还在栈中,所以LOW[2]=DFN[4]=5。返回1后,发现DFN[1]=LOW[1],把栈中节点全部取出,组成一个连通分量{1,3,4,2}。

至此,算法结束。经过该算法,求出了图2中全部的三个强连通分量{1,3,4,2},{5},{6}。

可以发现,运行Tarjan算法的过程中,每个顶点都被访问了一次,且只进出了一次堆栈,每条边也只被访问了一次,所以该算法的时间复杂度为O(N+M)

一、tarjan算法求强连通分量

代码:

void tarjan(int u){
    //求强连通分量
    dfn[u]=low[u]=++cnt;
    sk.push(u);
    instack[u]=1;
    for(auto v:G[u]){
        if(!dfn[v]){
            tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else if(instack[v])
            low[u]=min(low[u],dfn[v]);//求强连通分量时可以写成low[u]=min(low[u],low[v]);,但求割点时不行
    }
    if(low[u]==dfn[u]){
        ++Bcnt;//Bcnt为每个强连通分量的编号
        int x;
        do{
            x=sk.top();
            scc[x]=Bcnt;
            instack[x]=0;
            sk.pop();
        }while(u!=x);

    }
}

二、tarjan缩点

求出强连通分量后,以每个分量的scc编号为缩点后的顶点编号建立新图
代码

#include<iostream>
#include<algorithm>
#include<queue>
#include<set>
#include<stdlib.h>
#include<time.h>
#include<unordered_map>
#include<stack>
#include<cstdio>
#include<vector>
#include<cstring>
#include<string>
#include<map>
#include<cmath>
#include<bitset>
#define ll long long
#define inf 0x3f3f3f3f
#define bug(a) cout<<"* "<<a<<endl;
#define bugg(a,b) cout<<"* "<<a<<" "<<b<<endl;
#define buggg(a,b,c) cout<<"* "<<a<<" "<<b<<" "<<c<<endl;
using namespace std;
typedef pair<int,int> P;
const int N=1e5+10;
const ll mod=1e9+7;

int dfn[N],low[N],scc[N],cnt,Bcnt;
bool instack[N],vis[N];
int w[N],n,m,ans;
stack<int> sk;
vector<int> G[N],dag[N];//dag用于存储缩点后新图的边

void tarjan(int u){
    dfn[u]=low[u]=++cnt;
    sk.push(u);
    instack[u]=1;
    for(auto v:G[u]){
        if(!dfn[v]){
            tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else if(instack[v])
            low[u]=min(low[u],dfn[v]);
    }
    if(low[u]==dfn[u]){
        ++Bcnt;//Bcnt为每个强连通分量的编号
        int x;
        do{
            x=sk.top();
            scc[x]=Bcnt;
            instack[x]=0;
            sk.pop();
        }while(u!=x);

    }
}

int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
        cin>>w[i];
    for(int i=1;i<=m;i++){
        int u,v;
        cin>>u>>v;
        G[u].push_back(v);
    }
    for(int i=1;i<=n;i++){
        if(!dfn[i]){ //如果i节点没有遍历过,
            tarjan(i);
        }
    }
    for(int i=1;i<=n;i++){//建立新图
        for(auto v:G[i]){
            if(scc[v]!=scc[i]){
                dag[scc[i]].push_back(scc[v]);
                ind[scc[v]]++;//入度加1
            }
        }
    }
    return 0;
}

/*
6 8
1 2 3 2 1 1
4 5
5 6
6 4
1 2
2 3
3 1
6 2
4 1
*/

三、tarjan求割点、桥

求割点

u为割点应满足的条件:
(1) u为树根,且u有多于一个子树。
(2) u不为树根,且存在(u,v)为树枝边(或称父子边,即u为v在搜索树中的父亲),使得dfn(u)<=low(v).也就是u的子树中的v点无法到达u之前的点,所以u点去掉就是两个连通分支,所以u为割点

代码

void tarjan(int u){
    //求割点
    dfn[u]=low[u]=++cnt;
    int col=0;//计数子树个数
    for(auto v:G[u]){
        if(!dfn[v]){
            col++;
            tarjan(v);
            low[u]=min(low[u],low[v]);
            if((u==root&&col>1)||u!=root&&low[v]>=dfn[u])
                gedian[u]=1;//判断割点的两个条件
        }
        else low[u]=min(low[u],dfn[v]);
    }
}

求桥(割边)

一条边(u,v)是桥,当且仅当(u,v)为树枝边(即非负边),且满足dfn(u)<low(v)(前提是其没有重边)。

也就是,u的儿子v之间只有一条边(前提是无重边),且v点只能到u点到不了u点前,所以(u,v)边去掉就是两个连通分支,所以(u,v)为桥

注意:找桥的时候,要注意看有没有重边。有重边,则不是桥。

代码

void tarjan(int u){
    //求割点
    dfn[u]=low[u]=++cnt;
    int col=0;//计数子树个数
    for(auto v:G[u]){
        if(!dfn[v]){
            col++;
            tarjan(v);
            low[u]=min(low[u],low[v]);
            if(dfn[u]<low[v]&&mp[v][u]<=1&&mp[u][v]<=1)
                isce[v]=1;//判断割边的条件
        }
        else low[u]=min(low[u],dfn[v]);//求割点割边时不能写成low[u]=min(low[u],low[v]);
    }
}

举报

相关推荐

0 条评论