您的位置:首页 > 其它

Kosaraju算法---强联通分量

2014-08-19 14:25 92 查看

1、基础知识

所需结构:原图、反向图(若在原图中存在vi到vj有向边,在反向图中就变为vj到vi的有向边)、标记数组(标记是否遍历过)、一个栈(或记录顶点离开时间的数组)。

算法描叙

步骤1:对原图进行深度优先遍历,记录每个顶点的离开时间。

步骤2:选择具有最晚离开时间的顶点,对反向图进行深度优先遍历,并标记能够遍历到的顶点,这些顶点构成一个强连通分量。

步骤3:如果还有顶点没有遍历过,则继续进行步骤2,否则算法结束。

在dfs(bfs)中,一个结点的开始访问时间指的是遍历时首次遇到该结点的时间,而该结点的结束访问时间则指的是将其所有邻接结点均访问完的时间。

下面结合实例说明Kosaraju算法的基本策略。图1给出了一个有向图G。



Step1:假设从DFS在遍历时按照字母顺序进行,从4开始搜索得到以下搜索序列:[4,[5,[7,[6,[8,8],6],7],5],4] [1,[2,[3,3],2],1] 在遍历序列中每一个结点出现了两次,其中第一次出现的时间为该结点的开始访问时间,第二次出现的时间为该结点的结束访问时间。

根据结点结束访问时间的定义,在某一次调用DFS_SEARCH中,显然遍历时越早遇到的结点,相对结束访问时间也就越晚,因此在这一次调用DFS_SEARCH内结点结束的时间递减顺序与结点首次访问顺序一致。而对于不同次调用DFS_SEARCH,调用越晚其相对结点结束的时间也就越晚。根据上述分析,我们可以在第一次DFS遍历时一次得到内结点结束的时间的递减顺序。

 1)该图得到一个搜索树林共两个搜索树,一个以4为根(4能到达的所有点),一个以1为根(1能到达的所有点)。

 2)4能到的所有点在搜索序列里挨一起,4不能到的点形成的搜索序列肯定在其右边。

 3)搜索序列的左右括号是成对出现的,满足括号匹配的原则。某个节点两端括住的节点都是他能到达的所有结点。最先开始访问的节点(左括号),肯定也是其搜在 子树里面最后一个访问的节点(右括号)。

Step2:对图进行反向,再从1(4)[先1后4]点进行dfs,相当于在反向前的原图中求出所有能到达1(4)的点。

Step3:按结束时间从大到小进行搜索。我们看搜索序列,最后一个点肯定是第一遍深搜时得到的最后一棵树的根r。从它开始深搜,相当于求出所有能达到树根r的点。这样得到的搜索子树肯定是一个强联通分量。

图的存储表示形式:邻接矩阵[1,2标记原图和逆图的边]、邻接表。

Kosaraju算法的显著特征是:第一,引用了有向图的逆图;为了突出回向边,而对图进行转置,然后对转置的图按照之前得到的顶点序列进行DFS调用。第二,需要 对图进行两次DFS(一次在逆图上,一次在原图上)。而且这个算法依赖于一个事实:一个有向图的强连通分量与其逆图是一样的。由于算法的时间取决于两次DFS,因 此时间复杂度,对于稀疏图是O(V+E),对于稠密图是O(V²),可见这是一个线性算法。Kosaraju的结论是,在第二次DFS中,同一棵搜索树上的结点属于一个强连通分量。

与Tarjan算法的比较

在实际的测试中,时间:Tarjan算法的运行效率也比Kosaraju算法高30%左右。空间:Tarjan算法虽然比Kosaraju算法多用两个一维数组,但Kosaraju算法比Tarjan算法 多浪费建立一个反向图的空间,所以总体来说在空间复杂度上Tarjan比Kosaraju占优势。

隐藏性质:在第二次深搜选择树的顺序,若把求出来的每个强连通分量收缩成一个点 ,并且用求出每个强连通分量的顺序来标记收缩后的节点,那么这个顺序其实就是强连通分量收缩成点后形成的有向无环图的拓扑序列。

2、参考代码

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define MAXL 100

/*图的邻接表定义*/
typedef struct edge_node{  //边NODE
int key;
struct edge_node *next;
}ENode;
typedef struct{  //顶点
char vertex;
ENode *firstedge;
}VNode;
typedef VNode VList[MAXL];  //顶点表
typedef struct{  //图
//    int n,e;
VList vlist;
}ALGraph;
ALGraph *ALG=(ALGraph *)malloc(sizeof(ALGraph));   //原图邻接表
ALGraph *reverse_ALG=(ALGraph *)malloc(sizeof(ALGraph));   //逆图邻接表
int n,e;  //两表共用

int vis[MAXL];

int num_scc;
int scc[MAXL];

int cnt;
int ord[MAXL];

void Creat_ALGraph(void)  //建图[邻接表和逆邻接表]
{
int i,j;
char ch1,ch2;
ENode *ptr=(ENode *)malloc(sizeof(ENode));

scanf("%d,%d",&n,&e);
getchar();

for(i=0;i<n;i++)
{
scanf("%c",&ALG->vlist[i].vertex);
getchar();
ALG->vlist[i].firstedge=NULL;

reverse_ALG->vlist[i].vertex=ALG->vlist[i].vertex;  //逆表
reverse_ALG->vlist[i].firstedge=NULL;
}
for(int k=0;k<e;k++)
{
scanf("%c,%c",&ch1,&ch2);
getchar();

for(i=0;ch1!=ALG->vlist[i].vertex;i++);
for(j=0;ch2!=ALG->vlist[j].vertex;j++);

ptr=(ENode *)malloc(sizeof(ENode));
ptr->key=j;
ptr->next=ALG->vlist[i].firstedge;
ALG->vlist[i].firstedge=ptr;

ptr=(ENode *)malloc(sizeof(ENode));  //逆表
ptr->key=i;
ptr->next=reverse_ALG->vlist[j].firstedge;
reverse_ALG->vlist[j].firstedge=ptr;
}
}

void init_kosaraju(void)
{
num_scc=0;
cnt=0;
for(int i=0;i<n;i++)
{
vis[i]=0;
scc[i]=-1;
ord[i]=-1;
}
}

/*****************Kosaraju算法******************/
void orig_DFS(int u)
{
int son;
ENode *ptr=(ENode *)malloc(sizeof(ENode));

vis[u]=1;  //标记+遍历+入栈
ptr=ALG->vlist[u].firstedge;
while(ptr!=NULL)
{
son=ptr->key;
if(!vis[son])
orig_DFS(son);
ptr=ptr->next;
}
ord[cnt++]=u;
}

void reverse_DFS(int u)
{
int par;
ENode *ptr=(ENode *)malloc(sizeof(ENode));

scc[u]=num_scc; //归类+标记+遍历
vis[u]=1;
ptr=reverse_ALG->vlist[u].firstedge;
while(ptr!=NULL)
{
par=ptr->key;
if(!vis[par])
reverse_DFS(par);
ptr=ptr->next;
}
}

void SCC_Kosaraju()
{
int i;

for(i=0;i<n;i++)
if(!vis[i])
orig_DFS(i);

memset(vis,0,sizeof(vis));

for(i=n-1;i>=0;i--)
if(!vis[ord[i]])
{
num_scc++;
reverse_DFS(ord[i]);
}
}
/*********************************************/

int main(void)
{
Creat_ALGraph();

init_kosaraju();

SCC_Kosaraju();

printf("%d\n",num_scc);  //scc输出
for(int cn=1;cn<=num_scc;cn++)
{
for(int j=0;j<n;j++)
if(scc[j] == cn)
printf("%c ",ALG->vlist[j].vertex);  //ALG、reverse_ALG均可
printf("\n");
}

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