在有向图G中,如果两个顶点vi,vj间有一条从vi到vj的有向路径,同时还有一条从vj到vi的有向路径,则称两个顶点强连通(strongly connected)。如果有向图G的任意两个顶点都强连通,称G是一个强连通图。有向图的极大强连通子图(即:一个图的(强)连通子图,并且加入任何一个不在它的点集中的点都会导致它不再(强)连通。),称为强连通分量(strongly connected components)。如图:
红色圈起来的部分为该有向图的3个强连通分量。
求一个有向图的强连通分量有Kosaraju算法、Tarjan算法、Gabow算法。在Tarjan 算法和 Gabow 算法的过程中,只需要进行一次的深度优先搜索,而Kosaraju算法需要两次DFS,因而相对 Kosaraju 算法较有效率。这些算法可简称为SSC(strongly connected components)算法;但是kosaraju算法容易理解,也比较通用。本文只介绍kosaraju算法。其算法思想是:
step1:对原图G进行深度优先遍历,记录每个节点的离开时间。
step2:选择具有最晚离开时间的顶点,对逆图GT进行遍历,删除能够遍历到的顶点,这些顶点构成一个强连通分量。
step3:如果还有顶点没有删除,继续step2,否则算法结束。
理解这个算法需要理解强连通分量,以及理解逆图GT和原图G有着完全相同的连通分支,也就说,如果顶点s和t在G中是互达的,当且仅当s和t在GT中也是互达的。下面是C语言实现,用堆栈的先进后出的特性实现记录每个节点的离开时间。
/* Kosaraju求强连通分量邻接矩阵 */
#include "stdio.h"
#include "stdlib.h"
#define Vexnum 100
int map1[Vexnum][Vexnum]={0};
int nmap[Vexnum][Vexnum]={0};
int visited[Vexnum];
/*这部分是栈的定义和操作,可直接使用*/
struct StackSq{
int *stack1;
int top;
int MaxSize;
};
struct StackSq s;
void InitStack(struct StackSq *s)
{
s->MaxSize=100;/*置栈空间初始最大长度为100*/
s->stack1=(int *)malloc(10*sizeof(int));/*动态存储空间分配*/
s->top=-1;/*置栈为空*/
}
//1
void Push(struct StackSq* S,int x)
{
// if(S->top==S->MaxSize-1)againMalloc(S);/*若栈空间用完则重新分配更大的存储空间*/
S->top++;/*栈顶指针后移一个位置*/
S->stack1[S->top]=x;/*将新元素插入到栈顶*/
}
//2
int Pop(struct StackSq* S)
{
if(S->top==-1){
printf("栈空,无元素出栈!\n");
exit(1);
}
S->top--;
return S->stack1[S->top+1];
}
//3
int Peek(struct StackSq* s)
{
if(s->top==-1){
printf("栈空,无元素出栈!\n");
exit(1);
}
return s->stack1[s->top];
}
int EmptyStack(struct StackSq* S)
{
if(S->top==-1) return 1;
else return 0;
}
int N; /*顶点数目*/
int DFS1(int v) /* 深度遍历有向图G*/
{
visited[v] = 1;
for (int i = 1;i <= N;i++)
if (!visited[i] && map1[v][i])
DFS1( i );
Push(&s,v); /*这步很妙,时间顺序*/
return 0;
}
int DFS2( int v ) /* 深度遍历有向图G的逆图*/
{
visited[v] = 1;
printf( "%d,", v );
for (int i = 1;i <= N;i++)
if ( !visited[i] && nmap[v][i] )
DFS2( i );
return 0;
}
int kosaraju()
{
int i;
while (!EmptyStack(&s))
Pop(&s);
for(i=1;i<=N;i++)
visited[i]=0;
for (int i = 1;i <= N;i++)
if (!visited[i]) DFS1( i );
int t = 0;
for(i=1;i<=N;i++)
visited[i]=0;
while (!EmptyStack(&s))
{
int v = Peek(&s);
Pop(&s);
printf("{");
if (!visited[v])
{
t++;
DFS2( v );
}
printf("}");
}
return t;
}
/*测试*/
int main()
{
int M,v,e;
InitStack(&s);
scanf("%d%d",&N,&M); /*输入有向图的顶点数和边数*/
for (int i = 0; i < M; i++)
{
scanf("%d%d",&v,&e);
map1[v][e] = 1; /* 使用邻接矩阵保留图的信息*/
nmap[e][v] = 1; /* 使用邻接矩阵保留逆图的信息*/
}
printf("%d\n", kosaraju()); /* 输出连通分量个数 */
return 0;
}
程序输入 4 5 /*4个顶点,5条边,顶点从1开始编号*/
1 2
2 3
4 3
1 4
4 1
输出
{1,4}{}{2,}{3,}3 /*3个强连通分量{1,4},{2},{3}。
输出格式没弄好,自己还可用改下。