强连通分量算法1.1

思想讲解:

接下来是对算法流程的演示。

定义DFN(u)为节点u搜索的次序编号(时间戳),Low(u)为u或u的子树能够追溯到的最早的栈中节点的次序号。

从节点1开始DFS,把遍历到的节点加入栈中。搜索到节点u=6时,DFN[6]=LOW[6],找到了一个强连通分量。退栈到u=v为止,{6}为一个强连通分量。

image

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

image

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

image

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

image

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

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

求有向图的强连通分量还有一个强有力的算法,为Kosaraju算法。Kosaraju是基于对有向图及其逆图两次DFS的方法,其时间复杂度也是O(N+M)。与Trajan算法相比,Kosaraju算法可能会稍微更直观一些。但是Tarjan只用对原图进行一次DFS,不用建立逆图,更简洁。在实际的测试中,Tarjan算法的运行效率也比Kosaraju算法高30%左右。此外,该Tarjan算法与求无向图的双连通分量(割点、桥)的Tarjan算法也有着很深的联系。学习该Tarjan算法,也有助于深入理解求双连通分量的Tarjan算法,两者可以类比、组合理解。

求有向图的强连通分量的Tarjan算法是以其发明者Robert Tarjan命名的。Robert Tarjan还发明了求双连通分量的Tarjan算法,以及求最近公共祖先的离线Tarjan算法,在此对Tarjan表示崇高的敬意。

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<stack>
using namespace std;
const int maxn=1000+10;
int n,m; //点数,边数
int dfs_clock;//时钟
int scc_cnt;//强连通分量总数
vector<int> G[maxn];//G[i]表示i节点指向的所有点
int pre[maxn]; //时间戳
int low[maxn]; //u以及u的子孙能到达的祖先pre值
int sccno[maxn];//sccno[i]==j表示i节点属于j连通分量
stack<int> S;
 
void dfs(int u)
{
    pre[u]=low[u]=++dfs_clock;
    S.push(u);
    for(int i=0;i<G[u].size();i++)
    {
        int v=G[u][i];
        if(!pre[v])
        {
            dfs(v);
            low[u]=min(low[u],low[v]);
        }
        else if(!sccno[v])
        {
            low[u]=min(low[u],pre[v]);
        }
    }
    if(low[u] == pre[u])//u为当前强连通分量的入口
    {
        scc_cnt++;
        while(true)
        {
            int x=S.top(); S.pop();
            sccno[x]=scc_cnt;
            if(x==u) break;
        }
    }
}
 
//求出有向图所有连通分量
void find_scc(int n)
{
    scc_cnt=dfs_clock=0;
    memset(sccno,0,sizeof(sccno));
    memset(pre,0,sizeof(pre));
    for(int i=0;i<n;i++)
        if(!pre[i]) dfs(i);
}
int main()
{
    while(scanf("%d%d",&n,&m)==2&&n)
    {
        for(int i=0;i<n;i++) G[i].clear();
        while(m--)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            G[u].push_back(v);
        }
        find_scc(n);
        for(int i=0;i<n;i++)
            printf("%d号点属于%d分量\n",i,sccno[i]);
    }
}
/*
刘汝佳 训练指南P319测试图
输入:
12 17
0 1
1 2
1 3
1 4
4 1
2 5
5 2
4 5
4 6
5 7
6 7
8 6
6 9
9 8
7 10
10 11
11 9
输出:
0号点属于5分量
1号点属于4分量
2号点属于2分量
3号点属于3分量
4号点属于4分量
5号点属于2分量
6号点属于1分量
7号点属于1分量
8号点属于1分量
9号点属于1分量
10号点属于1分量
11号点属于1分量
*/

转载:https://www.byvoid.com/zhs/blog/scc-tarjan 

猜你喜欢

转载自blog.csdn.net/lanshan1111/article/details/83279153
今日推荐