tarjan算法在有向图求强连通分量
如果两个顶点间存在路径使得a可到达b b也可到达a,称两个顶点强连通(strongly connected)。如果有向图G的每两个顶点都强连通,称G是一个强连通图。非强连通有向图的极大强连通子图,称为强连通分量(strongly connected components)
tarjan算法是基于dfs深度优先搜索的
算法中使用dfn和low数组 dfn数组来记录每个节点的访问时间 也就是访问的次序,强连通分量在dfs时所有的节点可以通过一次dfs访问完成 ,所以在dfs时强连通分量在dfs树中是属于同一颗dfs树的,用low来记录强连通分量在dfs树的根节点在dfs时的访问时间(访问次序),当我们每访问一个顶点时就把这个顶点压入栈中,有一点不可否认在dfs树中子节点总是在根节点之后访问的 ,当low=dfn时这个节点就为强连通分量在dfs树种的根节点 我们只要把栈里在这个节点之后访问的都退栈 就是和这个节
属于同一个强连通分量的节点 在程序中实现起来就是 :
访问节点a 时 节点b和节点a相连接 (1)如果b节点已经被访问过在堆栈中则 a节点的low[a]=min(low[a],dfn[b]) 因为这种情况是dfs时先访问了b 然后再访问a 然后再通过a可以访问b 也就是说有a->b 的路径 也有b->a的路径 他们属于同一个强连通分量 b 可能是a在dfs树中根节点 (2)如果b节点没有被访问过 则说明b不是a的根 low[a]=min(low[a],low[b])
在这个图中 我们从1结点开始 dfs 下面模拟下程序执行的过程
low[1]=dfn[1]=1;1 入栈
low[2]=dfn[2]=2;2入栈
low[3]=dfn[3]=3;3入栈
low[4]=dfn[4]=4;4入栈
low[5]=dfn[5]=5;5入栈
当执行到5号节点时发现他和1相连 而1已经在栈中 low[5]=min(low[5] dfn[1]) =1;
在递归返回过程中
low[4]=min(low[5],low[4])=1;
low[3]=min(low[3],low[4])=1;
low[2]=min(low[2],low[3])=1;
low[1]=min(low[1],low[2])=1;
检测到low[1]=dfn[1] 于是退栈直到退到1 位置(1也退栈) 形成一个强连通分量
在整个过程中每个节点访问一次 每个边访问一次 所以算法复杂度为0(m+n)
下面是代码
#include <iostream>
#include <stdio.h>
#include <stack>
#include <sstream>
using namespace std;
const int nmax=50;
stack<int> ms;//栈用来存放dfs中的访问信息
int istack[nmax];//顶点是否在栈中的标志
int vis[nmax];//dfs的访问标志位
int time;//tarjan中的时间计数
int low[nmax];//强联通分量在dfs搜索树中的根节点的访问时间
int dfn[nmax];//每个节点的访问时间
struct edge
{
int adj_vex;
struct edge *next;
};
struct vertex
{
int vex;
struct edge *first;
};
struct graph
{
struct vertex vexs[nmax];
int verxnum;
}mg;
void init()
{
for(int i=0;i<nmax;i++)//清空dfs的访问标志 和顶点在栈中的访问标志
{
vis[i]=0;
istack[i]=0;
dfn[i]=0;
low[i]=0;
}
time=0;
}
void readgraph()
{
edge *temp;
int adjvex;
char a;
string b;
cout<<"please input the number of vertexs"<<endl;
cin>>mg.verxnum;
for(int i=1;i<=mg.verxnum;i++)//初始化连接表
{
mg.vexs[i].vex=i;
mg.vexs[i].first=NULL;
}
getchar();
for(int i=1;i<=mg.verxnum;i++)//输入每个顶点的边表信息
{
cout<<"please input the "<<i<<" vertex's edge "<<endl;
getline(cin,b);
istringstream mystream(b);
while(mystream>>adjvex)
{
temp=new edge;
temp->adj_vex=adjvex;
temp->next=mg.vexs[i].first;
mg.vexs[i].first=temp;
}
}
}
void tarjan(int s)
{
time++;
low[s]=dfn[s]=time;
vis[s]=1;
istack[s]=1;
ms.push(s);
for(struct edge* e=mg.vexs[s].first;e;e=e->next )//依次访问与s相连的顶点
{
int j=e->adj_vex;
if(!vis[j])//没有被访问过 不在堆栈中的点
{
tarjan(j);
if(low[s]>low[j])
low[s]=low[j];
}
else if(istack[j]&&low[s]>dfn[j])//在堆栈中的点 可能是s的根节点
low[s]=dfn[j];
}
if(dfn[s]==low[s])//当搜索的点是 强连通分量在搜索树中的根节点时 退栈打印出强连通分量
{
cout<<" the strongly connected components are :"<<endl;
int n;
do
{
n=ms.top();
ms.pop();
istack[n]=0;
cout<<n<<" ";
}while(n!=s);
cout<<endl;
}
}
int main(int argc,char *argv[])
{
readgraph();
init();
for(int i=1;i<=mg.verxnum;i++)
{
if(!vis[i])
{
tarjan(i);
}
}
while(1);
return 0;
}