您的位置:首页 > 其它

转一篇求图割点、连通分量的文章

2011-09-27 12:59 246 查看
我试图把强连通分量,割点,桥 通过一个统一的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

;//对于无向图,g[i][j]==0:无边,1:可重复访问的边,-1:不能访问<j,i>
//对于有向图,则按照常规g[i][j]==0 或1表示有无边,
int n;//图节点个数。
int d
;//d[i]为开始访问节点i的时间。
int l
;//lOW[i]
int p
;//p[i]为节点i的前驱节点。
int c
;//c[i]==0:白色,-1:灰色,1:黑色。//未访问,正访问,已经访问
int b
;//b[i]为节点i的度数。
int time;

//下面的f,gc似乎只有演示意义
int f
;//f[i]为结束访问节点i的时间。
int gc

;//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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: