有向图强连通分量的定义:在有向图G中,如果两个顶点vi,vj间(vi!= vj)有一条从vi到vj的有向路径,同时还有一条从vj到vi的有向路径,则称两个顶点强连通(strongly connected)。如果有向图G的每两个顶点都强连通,称G是一个强连通图。非强连通图有向图的极大强连通子图,称为强连通分量(stronglyconnected components)。
对于一幅无向图来说,只要2个顶点相连在同一路径上,那么这两个顶点就是连通的,从其中一个顶点可以到达另一顶点,求出哪些顶点是连通的也并不难。而对于有向图,受方向限制,两个连通的顶点并不一定能互相到达。如果两个连通的顶点可以相互到达,那么这两个顶点是强连通的。如果将图中所有的互相强连通的顶点划为一组,那么这组顶点就是强连通分量。高效求出强连通分量的算法有Kosaraju算法,Tarjan算法,Gabow算法。我表示我只能看懂Gabow算法。
据说Gabow算法和Tarjan算法的思想是一样的。每个强连通分量都有一个“根”,根是强连通分量中最先被检查的顶点。在一组强连通分量中,沿着根不断深度优先搜索,最终将会回到根,路上经过的顶点则属于这个强连通分量。
准备工作:
因为是演示,假定只有6个顶点,而且序号是连续的。#define MAXVERTEX 6
首先需要一个数组,用来保存当前顶点被检测的顺序。intOrder[MAXVERTEX];在这里将-1定为初始值,即没有被检测。Order[v]表示顶点v是第几个被检测的。除了这个数组,还需要一个变量,用来表示当前的顺序。intOrderNum=0;初始化为0表示还没开始检测。
结果也以数组的形式表示,为每个强连通分量分配一个不同的编号,而一个强连通分量中的所有顶点编号相同。int Part[MAXVERTEX];开始这个数组应该被初始化为-1,表示还不确定其中的顶点属于哪个强连通分量。同样还需要一个变量表示当前处理到第几个强连通分量。int PartNum=0;表示还没开始处理。
另外还需要2个辅助栈。第一个用来保存在一次深度优先搜索过程中遇到的顶点。stack<int> Path;这些顶点还没有被确定属于哪个强连通分量,在确定顶点属于某个强连通分量之后,就记录结果,将顶点弹出堆栈。第二个用来保存在一次深度优先搜索过程中遇到的根。stack<int>Root;
步骤1:
在所有顶点中,找一个没有被访问过的节点v,以v为参数执行步骤2。否则完成。
步骤2:
记录v的访问顺序。
将v压入堆栈Path和Root。
如果v指向其它的邻接顶点,那么对于每个邻接顶点next:
1) 如果没有访问过,则以next为参数,递归执行步骤2。
2) 如果访问过,而且没有确定它属于哪个强连通分量,弹出Root栈中next之后所有的顶点。
如果Root栈的顶元素等于v,那么在Part中记录顶点对应的的强连通分量。
最后递归返回。
代码:
#include <Windows.h>
#include <stack>
#include <vector>
using namespace std;
#define MAXVERTEX 6 //the number ofvertex
vector<int> Edge[MAXVERTEX]; //gragh
int Order[MAXVERTEX];//被检测顺序
int OrderNum=0;
int Part[MAXVERTEX]; // 连通变量的标号;
int PartNum=0;
stack<int> Path; //record dfs path;
stack<int> Root;//
void Gabow(int v)
{
Order[v]=++OrderNum; //v the order of visiting ;
Path.push(v);
Root.push(v);
for (inte=0;e<Edge[v].size();e++)
{
int next=Edge[v][e]; //neighbor
if (Order[next]==-1) //whether to visited
Gabow(next);
else if (Part[next]==-1)//visited and not label the connectivity;
{
while(Order[Root.top()]>Order[next])
Root.pop();
}
}
if (v==Root.top())
{
Root.pop();
PartNum++;
int Top;
do
{
Top=Path.top();
Part[Top]=PartNum;
Path.pop();
} while (Top!=v);
}
}
int _stdcall WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTRlpCmdLine,int nShowCmd)
{
Edge[0].push_back(1);Edge[0].push_back(2);
Edge[1].push_back(3);
Edge[2].push_back(3);Edge[2].push_back(4);
Edge[3].push_back(0);Edge[3].push_back(5);
Edge[4].push_back(5);
//-1的十六进制表示为0xFFFFFFFF,所以memset没错
memset(Order,-1,sizeof(Order));
memset(Part,-1,sizeof(Part));
for (int v=0;v<MAXVERTEX;v++)
if (Order[v]==-1)
Gabow(v);
WCHAR Tmp[100]={0};
WCHAR Result[200]={0};
for (int i=1;i<PartNum+1;i++)
{
wsprintf(Tmp,TEXT("第%d组强连通分量:"),i);
wcscat(Result,Tmp);
for (intv=0;v<MAXVERTEX;v++)
{
if (Part[v]==i)
{
wsprintf(Tmp,TEXT("%d"),v);
wcscat(Result,Tmp);
}
}
wcscat(Result,TEXT("\n"));
}
MessageBox(NULL,Result,TEXT("Title"),MB_OK);
return 0;
}
vector型数组Edge表示图的邻接表形式。图是这个样子的:
0 → 1
↓↖ ↓
2 → 3
↓ ↓
4 → 5
WinMain函数中的循环逐个检查未被检查过的顶点,调用Gabow函数。
Gabow函数检测一个顶点v,将顶点v标记,并分别放入栈Path和Root中。
如果顶点v指向其它顶点,则对于每个指向的顶点next:
如果next是没有检测过的顶点(Order[next]不为-1),递归地调用Gabow函数,继续进行深度优先搜索检测。
如果next已经检测过,而且已经确定它所属的强连通分量(Part[next]!=-1),说明对它的检测已完成,且只能由v到next,不能由next到v。它现在不在栈中,忽略。
如果next已经检测过,但没有确定其所属的强连通分量(Part[next]==-1),说明现在发现了一个有向环。由next出发,到vi,到vj,到vk……到v,又到next。这些顶点都是强连通的。其中next是这个强连通分量的根,它最早进入Root栈,现在还在栈中。根据保存的顶点检查次序,将Root栈中next之后进入的顶点全部删除,仅保留根next。
其实每一次将顶点放入Root,都可以理解为每个检查到的顶点是一个单独的强连通分量,根就是它自己。直到发现下一个顶点在不久前检查过,确定了从那个顶点到这个顶点之间是一个环,才把这些顶点串了起来,此时最早检查的那个顶点就是它们的根。
对v所有指向的next进行同样的步骤继续检测,直到一个顶点没有邻接顶点,或一个顶点指向的所有顶点都已被检测。
现在已经能确定Path中的顶点所属的强连通分量了。获得Root最顶部的根顶点,这是最近的根顶点。Path中栈顶到这个根顶点之间的顶点为一个强连通分量。对比当前顶点v是否是这个根顶点,如果不是,直接返回,回到上一个检查的顶点进行对比。如果是,就删除Root顶部的根顶点,将强连通分量编号PartNum+1,同时将Path中栈顶到这个根顶点之间的顶点不断弹出,记录进Part数组中。
在递归的返回途中,如果发现某个顶点的下一个顶点没有被检测过,还会继续DFS,执行相同的步骤。不过,这些顶点就不一定与这个顶点强连通了。
这一系列深度优先搜索的递归完成之后,会检测图中的下一个顶点,如果下一顶点未被检测,将以同样的方式处理这个顶点。
流程图:
0 → 1
↓↖ ↓
2 → 3
↓ ↓
4 → 5
Order |
1 |
-1 |
-1 |
-1 |
-1 |
-1 |
Part |
-1 |
-1 |
-1 |
-1 |
-1 |
-1 |
Path |
0 |
|
|
|
|
|
Root |
0 |
|
|
|
|
|
0 → 1
↓↖ ↓
2 → 3
↓ ↓
4 → 5
Order |
1 |
2 |
-1 |
-1 |
-1 |
-1 |
Part |
-1 |
-1 |
-1 |
-1 |
-1 |
-1 |
Path |
0 |
1 |
|
|
|
|
Root |
0 |
1 |
|
|
|
|
0 → 1
↓↖ ↓
2 → 3
↓ ↓
4 → 5
Order |
1 |
2 |
-1 |
3 |
-1 |
-1 |
Part |
-1 |
-1 |
-1 |
-1 |
-1 |
-1 |
Path |
0 |
1 |
3 |
|
|
|
Root |
0 |
1 |
3 |
|
|
|
0 → 1
↓↖ ↓
2 → 3
↓ ↓
4 → 5
Order |
1 |
2 |
-1 |
3 |
-1 |
-1 |
Part |
-1 |
-1 |
-1 |
-1 |
-1 |
-1 |
Path |
0 |
1 |
3 |
|
|
|
Root |
0 |
|
|
|
|
|
0 → 1
↓↖ ↓
2 → 3
↓ ↓
4 → 5
Order |
1 |
2 |
-1 |
3 |
-1 |
4 |
Part |
-1 |
-1 |
-1 |
-1 |
-1 |
-1 |
Path |
0 |
1 |
3 |
5 |
|
|
Root |
0 |
5 |
|
|
|
|
0 → 1
↓↖ ↓
2 → 3
↓ ↓
4 → 5
Order |
1 |
2 |
-1 |
3 |
-1 |
4 |
Part |
-1 |
-1 |
-1 |
-1 |
-1 |
1 |
Path |
0 |
1 |
3 |
|
|
|
Root |
0 |
|
|
|
|
|
0 → 1
↓↖ ↓
2 → 3
↓ ↓
4 → 5
Order |
1 |
2 |
5 |
3 |
-1 |
4 |
Part |
-1 |
-1 |
-1 |
-1 |
-1 |
1 |
Path |
0 |
1 |
3 |
2 |
|
|
Root |
0 |
2 |
|
|
|
|
0 → 1
↓↖ ↓
2 → 3
↓ ↓
4 → 5
Order |
1 |
2 |
5 |
3 |
-1 |
4 |
Part |
-1 |
-1 |
-1 |
-1 |
-1 |
1 |
Path |
0 |
1 |
3 |
2 |
|
|
Root |
0 |
|
|
|
|
|
0 → 1
↓↖ ↓
2 → 3
↓ ↓
4 → 5
Order |
1 |
2 |
5 |
3 |
6 |
4 |
Part |
-1 |
-1 |
-1 |
-1 |
-1 |
1 |
Path |
0 |
1 |
3 |
2 |
4 |
|
Root |
0 |
4 |
|
|
|
|
0 → 1
↓↖ ↓
2 → 3
↓ ↓
4 → 5
Order |
1 |
2 |
5 |
3 |
6 |
4 |
Part |
-1 |
-1 |
-1 |
-1 |
-1 |
1 |
Path |
0 |
1 |
3 |
2 |
4 |
|
Root |
0 |
4 |
|
|
|
|
0 → 1
↓↖ ↓
2 → 3
↓ ↓
4 → 5
Order |
1 |
2 |
5 |
3 |
6 |
4 |
Part |
-1 |
-1 |
-1 |
-1 |
2 |
1 |
Path |
0 |
1 |
3 |
2 |
|
|
Root |
0 |
|
|
|
|
|
0 → 1
↓↖ ↓
2 → 3
↓ ↓
4 → 5
Order |
1 |
2 |
5 |
3 |
6 |
4 |
Part |
3 |
3 |
3 |
3 |
2 |
1 |
Path |
|
|
|
|
|
|
Root |
|
|
|
|
|
|
Written By Wolfspirit(1152401936)
2013年4月25日6:30