/*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