【转】有向图的强连通分量,有向图的割点和桥,无向图的割点和桥

http://hi.baidu.com/deyaliu/blog/item/ac4ae30a01e8afd97bcbe1ff.html

 

我试图把强连通分量,割点,桥 通过一个统一的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

综合参考了

http://www.cnblogs.com/woodfish1988/archive/2007/06/28/798739.html

改正了其中一个小的失误:

“即对于u的子节点v, u是割点的条件 (p[u] == 0 && b[v] > 1) || (p[u] > 0 && l[v] >= d[u]) ”

应该为b[u] > 1

sample input1 样例来自

http://www.byvoid.com/blog/scc-tarjan/

posted @ 2011-05-12 16:04  羽落无声  阅读(2327)  评论(0编辑  收藏  举报