前面的文章介紹瞭如何用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縮點的博客徹底一致。
下一篇講點數學,別忘了來聽課。