1. 程式人生 > >tarjan演算法入門(二)——無向圖的雙連通分量(未完成)

tarjan演算法入門(二)——無向圖的雙連通分量(未完成)

一.雙連通分量.

無向圖的雙連通分量分為兩種,一種是點雙連通分量,簡稱v-DCC;一種是邊雙連通分量,簡稱e-DCC.

一個v-DCC的概念即為一張沒有割點的圖,一個e-DCC的概念即為一張沒有割邊的圖.

一張極大雙連通分量子圖的是指沒有一張雙連通分量子圖完全包含這張子圖的所有點且點數大於這張子圖.

 

二.雙連通分量的一些性質.

首先,我們可以發現,一張點數小於等於2的圖一定是一個v-DCC.

若一個v-DCC的點數大於2,顯然,v-DCC內的任意兩點之間都有兩條沒有點相交的簡單路徑.

而很明顯任意一個e-DCC,任意一條邊都在一個簡單環內.

 

三.e-DCC的求法.

定理1:去掉所有割邊後,任意一個連通塊都是一個e-DCC.

顯然定理1成立.

那麼我們只需要做一遍tarjan演算法求出所有割邊,將割邊打上標記後再進行一遍dfs,就可以求出所有的e-DCC.

程式碼如下:

void tarjan(int k,int fa){
  dfn[k]=low[k]=++num;
  for (int i=lin[k];i;i=e[i].next){
    int y=e[i].y;
    if (!dfn[y]){
      tarjan(y,i);
      low[k]=min(low[k],low[y]);
      if (low[y]>dfn[k]) e[i].br=e[i^1].br=1;
    }else if (i^fa^1) low[k]=min(low[k],dfn[y]);
  }
}
void dfs(int k){
  edcc[k]=dcc;
  for (int i=lin[k];i;i=e[i].next)
    if (!edcc[e[i].y]&&!e[i].br) dfs(e[i].y);
}
void contract(){
  for (int i=1;i<=n;i++)
    if (!dfn[i]) tarjan(i,0);
  for (int i=1;i<=n;i++)
    if (!edcc[i]) ++dcc,dfs(i);
}

 

四.e-DCC的縮點.

將一張無向圖每一個e-DCC都縮成一個點之後,顯然我們會得到一個森林(如果原圖連通則是一棵樹).

那麼縮點其實就是跑一遍e-DCC之後將得到的關係存到另一個鄰接表中.

至於怎麼構建這棵樹,我們可以列舉所有的邊,當這條邊是一條割邊的時候,就將它的頂點所對應的樹點連線起來

程式碼如下:

void insc(int x,int y){
  ec[++tc].y=y;
  ec[tc].next=linc[x];
  linc[x]=tc;
}
void tarjan(int k,int fa){
  dfn[k]=low[k]=++num;
  for (int i=lin[k];i;i=e[i].next){
    int y=e[i].y;
    if (!dfn[y]){
      tarjan(y,i);
      low[k]=min(low[k],low[y]);
      if (low[y]>dfn[k]) e[i].br=e[i^1].br=1;
    }else if (i^fa^1) low[k]=min(low[k],dfn[y]);
  }
}
void dfs(int k){
  edcc[k]=dcc;
  for (int i=lin[k];i;i=e[i].next)
    if (!edcc[e[i].y]&&!e[i].br) dfs(e[i].y);
}
void contract(){
  for (int i=1;i<=n;i++)
    if (!dfn[i]) tarjan(i,0);
  for (int i=1;i<=n;i++)
    if (!edcc[i]) ++dcc,dfs(i);
  for (int i=2;i<=t;i++){
    int x=e[i^1].y,y=e[i].y;
    if (edcc[x]==edcc[y]) continue;
    insc(edcc[x],edcc[y]);
  }
}