强连通分量SCC(Tarjan)

什么叫强连通分量呢~

有向图强连通分量在有向图G中,
如果两个顶点vi,vj间(vi>vj)有一条从vi到vj的有向路径,同时还有一条从vj到vi的有向路径,则称两个顶点强连通(strongly connected)。
如果有向图G的每两个顶点都强连通,称G是一个强连通图。
有向图的极大强连通子图,称为强连通分量(strongly connected components)。

举个例子:
这里写图片描述

一般来讲,我们选择的都是常数较小的线性算法:Tarjan

实际上,学习过无向图的割点之后,
SCC的tarjan算法更好理解

tarjan ta老人家也是用dfs解决这个问题的
考虑强连通分量C,设其阿红第一个被发现的点为x,
则C中的其他点都是x的后代
我们希望在x访问完成时立即输出C,现在问题的关键就是判断一个点是否是SCC中第一个被发现的点

如图是一棵dfs树
虚线表示一条或多条边
这里写图片描述
假设我们正在判断u是不是SCC中的第一个点,

  • 如果我们发现从u的子节点出发可以到达u的祖先w,显然u,v,w在一个SCC中
    因此u不是第一个发现的结点

  • 如果我们发现从v出发最多只能到达u,那么u就是SCC中第一发现的结点

注意

这里的到达,只能通过当前SCC中的点,而不能通过已经确定的SCC中的点

很像无向图割点的low数组
所以我们也可以用类似的方法维护出某一点能够到达的最早祖先

附上一张图
这里写图片描述

vector<int> G[N],G2[N];
stack<int> S;
int tot,cnt,pre[N],cnt,low[N],scc[N];

void dfs(int now)
{
    pre[now]=low[now]=++tot;
    S.push(now);   //
    for (int i=0;i<G[now].size;i++)
    {
        int v=G[now][i];
        if (!pre[v])   //之前没有访问过
        {
            dfs(v);
            low[now]=min(low[now],low[v]);
        } 
        else if (!scc[v])   //ta不在之前得到的scc中
        {
            low[now]=min(low[now],pre[v]);
        } 
    }
    if (low[now]==pre[now])
    {
        cnt++;
        for(;;)
        {
            int x=S.top(); S.pop();
            scc[x]=cnt;
            if (x==now) break;
        }
    }
}

void find(int n)
{
    cnt=0; tot=0;
    S.clear();
    memset(low,0,sizeof(low));
    memset(pre,0,sizeof(pre));
    for (int i=0;i<n;i++)
        if (!pre[i]) 
           dfs(i);
}
原文地址:https://www.cnblogs.com/wutongtong3117/p/7673584.html