您的位置:首页 > 其它

有向图的强连通分量(邻接矩阵图表示法)

2013-06-25 20:34 211 查看
有向图的强连通分量的求解是个经典问题,但是如果不深刻算法的道理,很难自己实现出来。

首先解释下算法的道理。

1、什么是强连通分量?书上定义是其中任意两个点可以相互到达的一个顶点集合。如果形象化的描述,强连通是这样的一种点的集合,这些顶点是由若个个环扩展来的(想象下图形),比如一个环A→B→C→A,然后再环的基础上加一条起点与终点都在环上的路径(如B→E→F→C),那么这个图形仍然是强联通的,用这种方法一直扩展到找不到这样的路径了,就找到了一个极大强连通分量。

2、怎么在强联通分量的基础上理解图?我们知道了强连通分量的直观理解了,如何在此基础上组成图呢,显然如果图包含了多个强连通分量,那么把这些强联通分量看成一个点,那么这些点肯定不能组成环(否则就连通起来了),不能有环的图等价于一个拓扑图(拓扑图的定义省略了)。总而言之,图就是由多多个强连通分量组成的拓扑序列。

3、什么是图的逆转?图的逆转就是把图中所以边的方向逆转。在理解前两点的基础上,我们知道把一个图逆转后,他的强连通分量是不变的。为什么?因为对于环来说只是换了个旋转方向,对于拓扑序列(由强联通分量之间组成)来说,逆向也不会产生环。

4、为什么深度优先遍历会体现图的结构?书上总是说深度优先遍历能体现图的结构,怎么体现的呢,对于有向图来说,体现的是顶点的拓扑排序。在深度优先遍历过程中有一个回溯过程,每次回溯的时候是当前顶点结束访问的时候。我们还是以强联通分量组成的拓扑系列才思考图,假设有三个强连通分量C1 C2 C3,他们的拓扑排序为C1→C2→C3,假设我们从C2中的一个顶点开始深度优先遍历,那么在C3所有顶点都结束访问后,C2才有可能结束所有点的访问,同理只有C2所有顶点结束访问后C1才有可能结束所有顶点的访问。很容易推理出最后一个结束访问的顶点一定属于C1连通分量。所以每个点的结束访问时间,如果逆序排列,可以几乎模拟拓扑排序中的顶点顺序。

注意这儿用的是几乎,因为逆序排列后不是严格按照(C1所有顶点)然后(C2所有顶点)然后(C3顶点)这样的顺序,而是把排列后的序列中去掉所有C1中的点,下一个点一定属于C2,同理再去点C2中所有点,下一个一定是C3中的(这个有点废话了 毕竟只有3个分量 哈哈)。

5、为什么算法把图逆转? 之前说了逆转后连通分量不变,还有一个好处是,逆转后如果按照原图的拓扑顺序去深度优先遍历,那么各个连通分量间就不会相互跨越访问!

如上面的例子,把C1→C2→C3逆转后变为C1←C2←C3,再按照原先的顺序深度遍历,那么遍历过程中一定不会从C1跨越到C2了,这个时候每次遍历到的点的集合就是一个强连通分量。

解释完了,理解了上面5点,算法也就很容易理解了。

算法:

1、第一次深度优先遍历图,标记每个顶点的结束访问时刻。

2、按照每个顶点的结束访问时刻逆序排列,

3、依排列顺序,选择一个未被访问的顶点深度优先遍历图。

4、如果图还有未被访问的顶点,连通分量加一,返回第3步。

下面是实现代码:

#include <iostream>

using namespace std;

#define N 10

int Vertex
={0};//保存顶点信息

int Arc

={0};//保存边的信息 这儿使用的是邻接矩阵图

int Visited
={0};//给第一个深度遍历用

int FinishedTime
={0};//保存每个顶点访问完成的时刻

int Visited1
={0};//给第二个深度遍历用 算法结束后 里面对应的是每个顶点属于的连通分量编号

int cnt=1;//计数用

int Level=1;//用来计算属于的连通分量编号

void DFS1(int a);

void DFS2(int a);

void Reverse();

int FindMax();

void show();

void main()

{

while(cin)//初始化图的信息 这儿设定图有10个顶点

{

cout<<"请输入边:\n";

int a,b;

cin>>a>>b;

if(cin)

Arc[a][b]=1;

}

for(int i=0;i!=N;++i)//第一次深度优先遍历 标记顶点完成时间

{

if(Visited[i]==0)

DFS1(i);

}

Reverse();//图的边求逆

while(cnt>1)

{

int t=FindMax();

DFS2(t);

Level++;

}

show();

::system("pause");

}

void DFS1(int a)

{

Visited[a]=1;

for(int i=0;i!=N;++i)

{

if(Visited[i]==0&&Arc[a][i]==1)

DFS1(i);

}

FinishedTime[a]=cnt++;

}

void DFS2(int a)

{

Visited1[a]=Level;

cnt--;//访问节点计数

for(int i=0;i!=N;++i)

{

if(Visited1[i]==0&&Arc[a][i]==1)

{

DFS2(i);

}

}

}

void Reverse()

{

for(int i=0;i!=N;++i)

for(int j=0;j<i;++j)

{

int t=Arc[i][j];

Arc[i][j]=Arc[j][i];

Arc[j][i]=t;

}

}

int FindMax()

{

int max=-1;int position=-1;

for(int i=0;i!=N;++i)

{

if(FinishedTime[i]>max&&Visited1[i]==0)

{

max=FinishedTime[i];position=i;

}

}

return position;

}

void show()

{

for(int i=1;i!=Level;++i)

{

cout<<"连通分量"<<i<<":\n";

for(int j=0;j!=N;++j)

{

if(Visited1[j]==i)

cout<<j<<" ";

}

cout<<endl;

}

}

测试输出:

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