在一个无向连通图中,如果任意去掉一个定点i及依附于i的所有边后得到的图仍然连通,
则称该图为“2-连通图”。否则,若得到多个连通分量,则该图不是双连通的,顶点i被称为
“割点 ”。
简单的说,在双连通图中,任何一对顶点都至少存在两条路径可以互相到达。图的连通
性不会任何一个顶点的影响。这个性质具有许多重要的应用价值,例如现实中的通讯网络部署,
出于可靠性和容错性的考虑,在结构上应考虑多连通性,尽量避免割点的存在。这样就算一个
通信节点损毁,也不会对其他节点的通信造成影响。
无向图的极大双连通子图被称为2-连通分量。一个无向图都具有一个或多个2-连通分量。
如果图本身是一个双连通图,那么它本身是自己唯一的2-连通分量。一个无向图具有2个或以
上2分量时,不难看出它的任意两个2分量最多只可能有一个公共顶点。因为如果有多于一个公
共顶点,就可以证明这两个2分量实际上可以组成一个更大的2分量,从而引起矛盾。进一步还
可以得出同一条边不可能处于多个二分量之中,因为一条边有两个顶点,如果一条边属于多个
二分量,则相当于两个顶点可以同时处于多个二分量之中,这与前述矛盾。
综上,所有二分量实际上把图划分为了不相交的子集,连接不同二分量的边成为割边。
可以利用深度优先搜索求2-连通分量。如果生成树的root是割点,当且仅当它有多于一个
子女。如果生成树的非根节点是割点,当且仅当它没有一个后代可以通过后向边回到它的祖先。
根据这些条件,我们定义一个点的时间戳为深度优先数,代表着顶点在深度优先搜索时被访问
的先后次序,记为D。如果D(i)<D(j),表明i点在serch过程中先于j点被访问。为了比较方便的
知道一个顶点是不是割点,对顶点定义参数low,low[i]为从i或其后代出发,通过后向边所能达
到的最小深度优先数。
low[i]=min{ D(i) , min{low[j]|j为i的子女} , min{D(k)|(i,k)为后向边} }
//edge为无向图边表,n为顶点数, e总边数
void TwoCC(int n,int e){
//D,low
//S 栈保存二分量边
int *D=(int *)malloc(sizeof(int)*(n+1));
int *low=(int *)malloc(sizeof(int)*(n+1));
int *S=(int *)malloc(sizeof(int)*(2*e));
memset(D,0,sizeof(D));
memset(low,0,sizeof(low));
for(int i=0; i<n; i++){
// 若未访问则以此为起点寻找二分量
if(D[i]==0)
DFS(i,-1,1,D,low,S,0);
}
}
// 从u做DFS,v是u之父,num为目前深度优先数
// top --> S
void DFS(int u,int v,int num,int D[],int low[],int S[],int top){
D[u]=low[u]=num++;
Edge* l = edge[u];
// 搜索所有与i邻接之顶点
while(l){
if( v!= l->dest && D[l->dest] < D[u] ){
// 连通分量边压栈
S[top++]=u;
S[top++] = l->dest;
}
if( D[l->dest] == 0 ){
DFS(l->dest,u,num,D,low,S,top);
low[u]=( low[u] < low[l->dest] )? low[u] : low[l->dest];
// 无后向边,分量结束
if( low[l->dest] == 0 ){
cout<<"A 2-CC found:"<<endl;
do{
int x = S[--top];
int y = S[--top];
cout<<x<<" "<<y<<endl;
if( x== u && y == l->dest ) break;
}while(1)
}
}
else
if( l->dest != v ){
// 如果l-dest已经被访问过,并且不等于父顶点v,证明有后向边更新low
low[u] = ( low[u] < D[l->dest] ) ? low[u] : D[l->dest];
}
l=l->next;
}
}