[Tarjan系列] Tarjan算法與有向圖的SCC

前面的文章介紹瞭如何用Tarjan算法計算無向圖中的e-DCC和v-DCC以及如何縮點。c++

本篇文章資料參考:李煜東《算法競賽進階指南》算法

這一篇咱們講如何用Tarjan算法求有向圖的SCC( 強連通份量 )已經如何縮點。spa

給定一張有向圖,若對於圖中任意兩個節點x和y,code

既有x到y的路徑,又有y到x的路徑,則該有向圖是一張「強連通圖」。blog

有向圖的極大連通子圖被稱爲「強連通份量」,即SCC。遞歸

一個環必定是強連通圖。若是既有x到y的路徑,又有y到x的路徑,那麼x和y就必定在一個環中。get

這就是Tarjan算法的原理:對於每一個點x,找到與它一塊兒能構成環的全部點。博客

下面介紹有向圖中的三種邊(x,y):數學

1. 樹枝邊:搜索樹中x是y的父節點it

2. 前向邊:搜索樹中x是y的祖先節點

3. 後向邊:搜索樹中y是x的祖先節點

4. 橫叉邊:除了以上三種狀況外的邊,知足dfn[y]<dfn[x]

這裏只給出簡單定義,再也不贅述。

咱們能夠發現,用Tarjan算法求SCC時,後向邊(x,y)能夠和搜索樹上從y到x的路徑構成一個環。

除後向邊外,經過橫叉邊也可能找到一條從y出發能回到x的祖先節點的路徑。

那麼爲了找到經過橫叉邊和後向邊構成的環,Tarjan算法在dfs的過程當中維護一個棧,當訪問到節點x時,棧中須要保存如下兩類節點:

1. 搜索樹上x的祖先節點,記爲集合anc(x)。設y∈anc(x),若存在一條後向邊(x,y),則(x,y)和y到x之間的路徑一塊兒造成環。

2. 已經訪問過,而且存在一條路徑到達anc(x)的節點。

設z時一個這樣的點,從z出發存在一條路徑到達y∈anc(x)。若存在橫叉邊(x,y),則(x,z)、z到y的路徑、y到x的路徑造成一個環。

綜上,棧中的節點就是能從x出發點的「後向邊」和「橫叉邊」造成環的節點。

至此,咱們引入追溯值low[x]的概念,有向圖的Tarjan算法裏面的定義和無向圖是不同的。

仍是設subtree(x)表示以x爲根的子樹。x的追溯值low[x]定義爲知足一下條件的節點的最小dfn:

1. 該點在棧中 2. 存在一條從subtree(x)出發的有向邊,以該點爲終點

根據以上定義,Tarjan算法根據如下步驟計算low[x]:

1. 當節點x第一次被訪問時,將x入棧,初始化low[x]=dfn[x]

2. 掃描從頭x出發的每條邊(x,y),若y沒被訪問過,則說明(x,y)時樹枝邊,遞歸訪問y,從y回溯以後,令low[x]=min(low[x],low[y]),若y被訪問過且y在棧中,令low[y]=min(low[x],dfn[y])

3. 從x回溯以前,判斷是否有low[x]=dfn[x],若成立,則不斷從棧中彈出節點直至x出棧。

SCC的斷定法則:

在上面的計算步驟3中,從棧中從x到棧頂的全部節點構成一個SCC。

少廢話,上代碼!

好der~

#include<bits/stdc++.h>
#define N 1000010
using namespace std;
inline int read(){
    int data=0,w=1;char ch=0;
    while(ch!='-' && (ch<'0'||ch>'9'))ch=getchar();
    if(ch=='-')w=-1,ch=getchar();
    while(ch>='0' && ch<='9')data=data*10+ch-'0',ch=getchar();
    return data*w;
}
struct Edge{
    int nxt,to;
    #define nxt(x) e[x].nxt
    #define to(x) e[x].to
}e[N<<1];
int head[N],tot=1;
inline void addedge(int f,int t){
    nxt(++tot)=head[f];to(tot)=t;head[f]=tot;
}
int dfn[N],low[N],stk[N],ins[N],c[N];
vector<int> scc[N];
int n,m,cnt,top,num;
void tarjan(int x){
    dfn[x]=low[x]=++cnt;
    stk[++top]=x,ins[x]=1;
    for(int i=head[x];i;i=nxt(i)){
        int y=to(i);
        if(!dfn[y]){
            tarjan(y);
            low[x]=min(low[x],low[y]);//搜索樹上的點
        }else if(ins[y])
            low[x]=min(low[x],dfn[y]);//y在棧中且y被訪問過了
   }
  if(dfn[x]==low[x]){ num++;int z;//新的一個SCC do{ z=stk[top--],ins[z]=0;//彈出棧頂元素z c[z]=num,scc[num].push_back(z);//z插入存第num個SCC的vector裏 }while(z!=x);//知道x被彈出棧 } } int main(){ n=read();m=read(); for(int i=1;i<=m;i++){ int x=read(),y=read(); addedge(x,y); } for(int i=1;i<=n;i++) if(!dfn[i])tarjan(i); for(int i=1;i<=num;i++){ printf("%d:",i); for(int j=0;j<scc[i].size();j++){ printf(" %d",scc[i][j]); } putchar(10); } return 0; }

SCC的縮點就很是簡單了,上面咱們已經用c[x]儲存了每一個點所在的SCC的編號,那咱們直接相似e-DCC的縮點,把每一個SCC縮成一個點,若c[x]≠c[y],咱們就在編號爲c[x]和c[y]的SCC中連一條邊就能夠獲得一個有向無環圖( DAG )。

代碼真的很是簡單,甚至不須要再跑一遍dfs。

給出代碼:

for(int x=1;x<=n;x++)
    for(int i=head[x];i;i=nxt(i)){
        int y=to(i);
        if(c[x]==c[y])continue;
        addedge_c(c[x],c[y]);
        }
//夠簡單了吧...

整個程序的代碼我就不貼出來了,建新圖和我前面e-DCC縮點的博客徹底一致。

下一篇講點數學,別忘了來聽課。

相關文章
相關標籤/搜索