强连通分支的定义:有向图G=(V,E)的一个强连通分支就是一个最大的顶点集合(不能再增加顶点的意思)C,对于C中的每一对顶点u,v,有u~v和v~u;即顶点u和v是互相可达的,注:不要求u和v满足:(u,v)属于E,即不要求u和v之间必须直接可达!
完全子图的定义:有向图G=(V,E)的一个完全子图就是一个顶点集合C,对于C中的每一对顶点u,v,有(u,v)属于E,注:要求u,v之间必须直接可达!
团的定义:有向图G的完全子图C是G的一个团<=>C不包含在G的更大的完全子图中,注:C本身可能并不是含顶点数最多的完全子图,C仅仅是满足了完全子图的定义同时还没有其它的完全子图能完全包含C。
最大团的定义:G的所有团中含顶点数最多的团。
强连通分支与最大团不是同一个问题!
强连通分支算法的理论证明
首先,设C和C'是有向图G=(V,E)中两个不同的强连通分支,如果u属于C,v属于C',且有(u,v)属于E,那么不可能从v出发能到达u。即不同的强连通分支之间要么没有边相连,要么只能是C的某些边指向C',或者C'某些边指向C。
其次,如果有(u,v)属于E,那么f(C)>f(C')(f(C)=max{f[u]|u属于C})。
然后,在G的转置图中,如果有(u,v)属于E的转置,u属于C,v属于C',那么,则f(C')>f(C)。即转置图中,任意两个不同强连通分支的边都是从(第一个在深度优先搜索中)有着更早的完成时间的分支指向有着较晚的完成时间的分支。
最后,在转置图中总是找有最大完成时间的点(且尚未被搜索过的)进行深搜,这个点的完成时间代表了它所在强连通分支的最大完成时间,而根据上面的理论,这个强连通分支只可能有发出的边指向比它有更大的完成时间的强连通同分支,而这与它本身是具有最大完成时间的前提矛盾,所以,从这点出发的深搜只能搜索到与这个点同处于一个强连通分支内的点;接着,再找一个没有被搜过的且完成时间在剩下的点里面是最大的点,深搜,如此往复,直到所有的强连通分支都被搜索了出来。
最大团的实现代码
时间复杂度:O(N*2^N)
#include<stdio.h>
const int N = 5;//图的顶点个数
int map[N+1][N+1];//图的顶点从1开始编号
int cn,bestn;//cn表示当前顶点个数;bestn表示最大团中的顶点个数
int x[N+1],bestx[N+1];//x表示当前解;bestx表示最有解---最大团;bestx[i] = 1表示顶点i在最大团中
int j;
void Backtrack(int i)
{
if(i>N)//如果顶点编号从0开始,这里得变成i>=N
{
if(cn>bestn)
{
for(j = 1; j<=N; ++j)bestx[j] = x[j];
bestn = cn;
return;
}
return;
}
int OK = 1;
for(j = 1; j<i; ++j)
{
if(x[j] && map[i][j] == 0)
{
OK = 0;
break;
}
}
if(OK)//注意“第i层”这个概念的理解,这里每一层表示一个顶点的选择,左边分支表示i位于当前解中
{
x[i] = 1;
++cn;
Backtrack(i+1);
x[i] = 0;
--cn;
}
if(cn+N-i>bestn)
{
x[i] = 0;
Backtrack(i+1);
}
}
int main()
{
int u,v;
freopen("in.txt","r",stdin);
while(scanf("%d %d",&u,&v)+1)
{
map[u][v] = 1;
map[v][u] = 1;//注意这里是无向图
}
Backtrack(1);
printf("The maxClique has %d vertexes.\n",bestn);
for(j = 1; j<=N; ++j)
{
if(bestx[j])
printf("%d ",j);
}
return 0;
}