1. 程式人生 > >(轉)Tarjan應用:求割點/橋/縮點/強連通分量/雙連通分量/LCA(最近公共祖先)

(轉)Tarjan應用:求割點/橋/縮點/強連通分量/雙連通分量/LCA(最近公共祖先)

應用 說明 lca ref father 無向圖 沒有 經理 遠的

本文轉載自:http://hi.baidu.com/lydrainbowcat/item/f8a5ac223e092b52c28d591c

作者提示:在閱讀本文之前,請確保您已經理解並掌握了基本的Tarjan算法,不會的請到http://hi.baidu.com/lydrainbowcat/blog/item/42a6862489c98820c89559f3.html閱讀。

基本概念:

1.割點:若刪掉某點後,原連通圖分裂為多個子圖,則稱該點為割點

2.割點集合:在一個無向連通圖中,如果有一個頂點集合,刪除這個頂點集合,以及這個集合中所有頂點相關聯的邊以後,原圖變成多個連通塊,就稱這個點集為割點集合

3.點連通度:最小割點集合中的頂點數。

4.割邊(橋):刪掉它之後,圖必然會分裂為兩個或兩個以上的子圖。

5.割邊集合:如果有一個邊集合,刪除這個邊集合以後,原圖變成多個連通塊,就稱這個點集為割邊集合

6.邊連通度:一個圖的邊連通度的定義為,最小割邊集合中的邊數。

7.縮點:把沒有割邊的連通子圖縮為一個點,此時滿足任意兩點之間都有兩條路徑可達。

註:求塊<>求縮點。縮點後變成一棵k個點k-1條割邊連接成的樹。而割點可以存在於多個塊中。

8.雙連通分量:分為點雙連通和邊雙連通。它的標準定義為:點連通度大於1的圖稱為點雙連通圖,邊連通度大於1的圖稱為邊雙連通圖。通俗地講,滿足任意兩點之間,能通過兩條或兩條以上沒有任何重復邊的路到達的圖稱為雙連通圖。無向圖G的極大

雙連通子圖稱為雙連通分量

Tarjan算法的應用論述:

1.求強連通分量(見上一篇文章,本文第一行有鏈接)、割點、橋、縮點:

對於Tarjan算法中,我們得到了dfn和low兩個數組,

low[u]:=min(low[u],dfn[v])——(u,v)為後向邊,v不是u的子樹;

low[u]:=min(low[u],low[v])——(u,v)為樹枝邊,v為u的子樹;

下邊對其進行討論:

若low[v]>=dfn[u],則u為割點,節點v的子孫和節點u形成一個塊。因為這說明v的子孫不能夠通過其他邊到達u的祖先,這樣去掉u之後,圖必然分裂為兩個子圖。這樣我們處理點u時,首先遞歸u的子節點v,然後從v回溯至u後,如果發現上述不等式成立,則找到了一個割點u,並且u和v的子樹構成一個塊。

void tarjan(int x)
{
v[x]=1,dfn[x]=low[x]=++num;
for(int i=head[x];i;i=next[i])
if(!v[ver[i]])
{
tarjan(ver[i]);
low[x]=min(low[x],low[ver[i]]);
if(dfn[x]<=low[ver[i]]) v[x]++;
}
else low[x]=min(low[x],dfn[ver[i]]);
if((x==1&&v[x]>2)||(x>1&&v[x]>1)) v[x]=2; else v[x]=1;//v[x]=2表示該點為割點,註意其中第一個點要特判
}

若low[v]>dfn[u],則(u,v)為割邊。但是實際處理時我們並不這樣判斷,因為有的圖上可能有重邊,這樣不好處理。我們記錄每條邊的標號(一條無向邊拆成的兩條有向邊標號相同),記錄每個點的父親到它的邊的標號,如果邊(u,v)是v的父親邊,就不能用dfn[u]更新low[v]。這樣如果遍歷完v的所有子節點後,發現low[v]=dfn[v],說明u的父親邊(u,v)為割邊。

void tarjan(int x)
{
vis[x]=1;
dfn[x]=low[x]=++num;
for(int i=head[x];i;i=next[i])
if(!vis[ver[i]])
{
p[ver[i]]=edge[i];//記錄父親邊
tarjan(ver[i]);
low[x]=min(low[x],low[ver[i]]);
}
else if(p[x]!=edge[i])//不是父親邊才更新
low[x]=min(low[x],dfn[ver[i]]);
if(p[x]&&low[x]==dfn[x]) f[p[x]]=1;//是割邊
}

2.求雙連通分量以及構造雙連通分量:

對於點雙連通分支,實際上在求割點的過程中就能順便把每個點雙連通分支求出。建立一個棧,存儲當前雙連通分支,在搜索圖時,每找到一條樹枝邊或後向邊(非橫叉邊),就把這條邊加入棧中。如果遇到某時滿足DFS(u)<=Low(v),說明u是一個割點,同時把邊從棧頂一個個取出,直到遇到了邊(u,v),取出的這些邊與其關聯的點,組成一個點雙連通分支。割點可以屬於多個點雙連通分支,其余點和每條邊只屬於且屬於一個點雙連通分支。

對於邊雙連通分支,求法更為簡單。只需在求出所有的橋以後,把橋邊刪除,原圖變成了多個連通塊,則每個連通塊就是一個邊雙連通分支。橋不屬於任何一個邊雙連通分支,其余的邊和每個頂點都屬於且只屬於一個邊雙連通分支。

一個有橋的連通圖,如何把它通過加邊變成邊雙連通圖?方法為首先求出所有的橋,然後刪除這些橋邊,剩下的每個連通塊都是一個雙連通子圖。把每個雙連通子圖收縮為一個頂點,再把橋邊加回來,最後的這個圖一定是一棵樹,邊連通度為1。

統計出樹中度為1的節點的個數,即為葉節點的個數,記為leaf。則至少在樹上添加(leaf+1)/2條邊,就能使樹達到邊二連通,所以至少添加的邊數就是(leaf+1)/2。具體方法為,首先把兩個最近公共祖先最遠的兩個葉節點之間連接一條邊,這樣可以把這兩個點到祖先的路徑上所有點收縮到一起,因為一個形成的環一定是雙連通的。然後再找兩個最近公共祖先最遠的兩個葉節點,這樣一對一對找完,恰好是(leaf+1)/2次,把所有點收縮到了一起。

3.求最近公共祖先(LCA)

在遍歷到u時,先tarjan遍歷完u的子樹,則u和u的子樹中的節點的最近公共祖先就是u,並且u和【u的兄弟節點及其子樹】的最近公共祖先就是u的父親。註意到由於我們是按照DFS順序遍歷的,我們可用一個color數組標記,正在訪問的染色為1,未訪問的標記為0,已經訪問到即在【u的子樹中的】及【u的已訪問的兄弟節點及其子樹中的】染色標記為2,這樣我們可以通過並查集的不斷合並更新,通過getfather實現以上目標。

void tarjan(int x)
{
fa[x]=x,color[x]=1;
int i,y;
for(i=head[x];i;i=next[i])
if(color[y=ver[i]]==0)
{
tarjan(y);
fa[y]=x;
}
for(i=headquery[x];i;i=nextquery[i])
if(color[y=query[i]]==2) ans[i]=get(y);
color[x]=2;
}

參考例題:Poj 1523、2942、3694、3352、3177 Tyvj P1111

註:本文部分內容摘自BYVoid神牛的Blog:http://www.byvoid.com/blog/biconnect/

(轉)Tarjan應用:求割點/橋/縮點/強連通分量/雙連通分量/LCA(最近公共祖先)