Tarjan演算法三大應用之雙連通分量
基本概念
定義1:
割點集合:點集
V′∈V ,若G 刪除了V′ 後不連通,但刪除了V′ 的任意真子集後G 仍然連通,則稱V′ 為割點集合割點:若某一結點就構成了割點集合,那麼稱此結點為割點或關節點。
點連通度:點數最少的割點集合
割邊集合:邊集
E′∈E ,若G 刪除了E′ 後不連通,但刪除了E′ 的任意真子集後G 仍然連通,則稱E′ 為割邊集合割邊:若某一結點就構成了割邊集合,那麼稱此邊為割邊或橋或關節邊。
邊連通度:邊數最少的割邊集合
塊:沒有割點的極大連通子圖
定義2:
如果一個無向連通圖的點連通度大於1,則稱該圖是點雙連通的,簡稱雙連通
如果一個無向連通圖的邊連通度大於1,則稱該圖是邊雙連通的,也簡稱雙連通。通俗來說就是不存在割邊。
可以看出,點雙連通與邊雙連通都可以簡稱為雙連通,它們之間是有著某種聯絡的。(雙連通圖一定既是點雙連通的又是邊雙連通的)
定義3:
在圖
G 的所有子圖G′ 中,如果G′ 是雙連通的,則稱G′ 為雙連通子圖。如果一個雙連通子圖G′ 它不是任何一個雙連通子圖的真子集,則G′ 為極大雙連通子圖。雙連通分量,就是圖的極大雙連通子圖。特殊的,點雙連通分支又叫做塊。
tarjan演算法求割點和橋
tarjan演算法求解雙連通分量的做法和求解有向圖的強連通分量做法類似。
點雙連通分量
求點雙連通分量也就是去找割點
在dfs搜尋樹中,如果點k滿足下列兩個條件之一,那麼k是割點:
- k 為深搜樹的根,且 k 的兒子個數
≥2 - k 為深搜樹的中間節點(k 既不是根也不是葉),且
low[son]>=dfn[k]
第一個條件很好理解,第二個條件son表示k的兒子,這句話的意思也就是說k的子孫中不會有某個點有追溯到k的祖先的邊。
這樣,對於求點連通分量演算法中low值的嚴格定義將更新為:
low(u)=Min
{
dfn(u)
low[v] v是u 的一個兒子
dfn[v] v與u鄰接,且邊(u,v)是一條返祖邊
}演算法具體細節見程式碼:
void tarjan(int p,int u) { dfn[u] = low[u] = ++ti; // 時間戳 int son = 0; // 當前節點兒子的個數 for (k=head[u]; k!=-1; k=edge[k].next) //前向星實現 { int v = edge[k].to; if (v == p) continue; if (dfn[v] == 0) { son++; tarjan(u,v); if (low[v] < low[u]) low[u] = low[v]; if((u!=1 && dfn[u]<=low[v]) || (u==1&&son>=2)) istcc[u]=1; // 條件符合,u是割點 } else low[u] = min(low[u],dfn[v]); } }
- k 為深搜樹的根,且 k 的兒子個數
邊雙連通分量
求邊雙連通分量也就是去找割邊
割邊的求解過程和求割點的方法類似,判斷方法是:
無向圖中的一條邊(u,v)是割邊,當且僅當(u,v)是生成樹中的邊,且滿足
dfn[u]<low[v] 。找到一條割邊就將割邊下面的邊連通分量出棧。
stack<int> St; void Tarjan(int p,int u) { dfn[u]=low[u]=++ti; St.push(u); vis[u]=1; for(int k=head[u];k!=-1;k=edge[k].next) { int v=edge[k].v; if(v==p)continue; if(!dfn[v]) { Tarjan(u,v); low[u]=min(low[u],low[v]); if(low[v] > dfn[u]){ cnt_bri++;//雙聯通分量塊的個數,多一個橋即多一個雙聯通分量 } else// if(vis[v]==1)//v在棧中,說明(u,v)是返祖邊 { low[u]=min(low[u],dfn[v]); } } if(dfn[u]==low[u])//將u所在的邊連通分量出棧 { int x; ++cnt;//cnt表示縮點後的連通分量 while(1) { x=St.top(); St.pop(); vis[x]=0; belong[x]=cnt; if(x==u)break; } } }