轉一篇求圖割點、連通分量的文章

我試圖把強連通分量,割點,橋 通過一個統一的DFS 融合在一起,主要根據是橋的兩端是割點,以及下面的定理。

/*file:SCC.c
在有向圖中,如果兩個兩個節點之間相互可達,則稱這兩個節點是強連通的Strongly Connected。
定理:每個強連通分量是深度優先搜索樹中的一棵子樹。
圖和它的逆圖的SCC相同。
u的傳遞閉包是u所在的SCC加上這個SCC縮爲一個點的後代。

在一個無向連通圖中,若把節點v去掉,原圖變成不連通的,則稱節點v爲割點;
若把一條邊e去掉,原圖變成不連通的,則稱邊e爲橋。

定義DFN(u)爲節點u在搜索樹中被遍歷到的次序編號。
定義LOW(u)爲節點u或u的子樹能夠追溯到的DFN最小的節點。

DFN(u)==LOW(u)<==>以u爲根的子樹上的所有節點形成一個強連通分量。
無向邊(u,v)是橋<==>(u,v)爲樹枝邊,且滿足DFN(u)<LOW(v)
一個節點u是割點<==>"(u,v)爲樹枝邊,且滿足DFN(u)<=LOW(v)"或u是有兩棵子樹的根。

DFN(u)==LOW(v)表明u,v之間存在兩條以上的邊,故自然(u,v)就不是橋了。
無向圖的DFS生成樹沒有橫叉邊,故其不同子樹一定屬於不同點連通分量。

根據定義,有
LOW(u) = min of {

DFN(u),
DFN(v),其中(u,v)爲後向邊(返祖邊),這又等價於DFN(v)<DFS(u)且v不是u的父親節點.
LOW(v),其中(u,v)爲樹枝邊(父子邊) }

c -- 使得每個節點只屬於一棵DFS生成樹。
d,f-- 時間戳:1,..,2|V|, d[u]<f[u], 在d[u]之前u是白色的,在d[u],f[u]之間是灰色的,之後是黑色的。
<u,v>是樹枝,若v是第一次被發現;是正向邊,若v是u的後裔非樹枝邊;是反向邊,v是u的祖先,環也被認爲是反向邊;是交叉邊,若是其他類型,連接同一/不同樹的兩個節點。

<u,v>是
樹枝或正向邊<==> d[u]<d[v]<f[v]<f[u]
反向邊<==> d[v]<d[u]<f[u]<f[v] //v爲灰色

交叉邊<==> d[v]<f[v]<d[u]<f[u] //v爲黑色

*/
#include<stdio.h>
#define N 128
int g[N][N];//對於無向圖,g[i][j]==0:無邊,1:可重複訪問的邊,-1:不能訪問<j,i>
//對於有向圖,則按照常規g[i][j]==0 或1表示有無邊,
int n;//圖節點個數。
int d[N];//d[i]爲開始訪問節點i的時間。
int l[N];//lOW[i]
int p[N];//p[i]爲節點i的前驅節點。
int c[N];//c[i]==0:白色,-1:灰色,1:黑色。//未訪問,正訪問,已經訪問
int b[N];//b[i]爲節點i的度數。
int time;

//下面的f,gc似乎只有演示意義
int f[N];//f[i]爲結束訪問節點i的時間。
int gc[N][N];//g[i][j]==0:無邊,1:樹枝邊,2:反向邊,3:正向邊,4:交叉邊。

void dfs_visit(int u)
{//以u爲根深度優先搜索圖。
   int v;
   c[u] = -1;//此處把u入棧.
   time++;d[u] = time; l[u] = time;

   for(v=1;v<=n;v++)if(g[u][v]>0)
   {
      if(c[v]==0)//白色,對於有向圖,此時一定有d[v]==0
      {
         g[v][u] = -1;//對於有向圖應當註釋掉
         gc[u][v] = 1;//此處<u,v>屬於重連通分量(無向圖的塊)
         b[u]++;
         p[v] = u;
         dfs_visit(v);
         if(l[v]<l[u])l[u] = l[v];
         g[v][u] = 1;//對於有向圖應當註釋掉

         if((p[u]==0 && b[u]>1) ||
            (p[u]>0 && l[v]>=d[u]))printf("cut point:%d\n",u);

         if(l[v]>d[u])printf("bridge:<%d,%d>\n",u,v);
      }else if(c[v]<0)//灰色,此時v一定在棧中
      {
         gc[u][v] = 2;//<u,v>爲反向邊
         if(l[v]<l[u])l[u] = l[v];
      }else  //黑色
      {
         gc[u][v] = d[v] > d[u]? 3:4;
      }
   }
   //此處開始檢查 if l[u]==d[u] then 棧中的所有節點形成一個SSC。
   //把棧內節點出棧並標記爲已經訪問
   c[u]=1;
   time++;f[u] = time;
}

int main()
{
   int u;
   build();
   for(u=1;u<=n;u++)if(c[u]==0)
   {
      p[u] = 0;
      dfs_visit(u);
   }
   return 0;
}

int build()
{
   int i,j;
   scanf("%d",&n);
   for(i=1;i<=n;i++)
   {
      while(1==scanf("%d",&j) && j)
      {
         g[i][j] = 1;
         g[j][i] = 1;//無向圖,對於有向圖需註釋掉
      }
   }
   return 0;
}
#if 0
無向圖
sample input1
6
2 3 0
4 0
4 5 0
6 1 0
6 0
0
sample output1
<nothing>
sample input2
4
2 3 0
3 4 0
0
0
sample output2
cut point:2
bridge:<2,4>

有向圖
sample input3=sample input1

sample output3

(gdb) p d
$81 = {0, 1, 2, 8, 3, 9, 4, 0 <repeats 121 times>}
(gdb) p l
$82 = {0, 1, 1, 8, 1, 9, 4, 0 <repeats 121 times>}
(gdb) p f
$83 = {0, 12, 7, 11, 6, 10, 5, 0 <repeats 121 times>}
按照f對節點排序:6,4,2,5,3,1
l[6]==d[6]故得一個SCC {6}
l[4]!=d[4],l[2]!=d[2],
l[5]==d[5],故SCC = {5}
l[3]==d[3],故SCC = {1,2,3,4}
程序輸出了
cut point:4
bridge:<4,6>
cut point:3
bridge:<3,5>
cut point:1
bridge:<1,3>

這裏理解爲“u是有向圖的割點,若去掉它之後使得某對節點不可達”。例如去掉4,使得2不能到達6;2不是割點,因爲去掉它之後圖的任意兩點可達(雖然不是雙向可達)。
類似地,“(u,v)是有向圖的橋,若去掉它之後使得某對節點不可達”。例如
去掉橋<3,5>之後,3不能到達5。


應用例子:
割點 1523,1144
橋 3352
SCC 2553
拓撲排序:每次把節點變黑的同時加到鏈表首部,有向圖是DAG當且僅當沒有黑邊。

#endif

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章