在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强连通(strongly connected)。如果有向图G的每两个顶点都强连通,称G是一个强连通图。非强连通图有向图的极大强连通子图,称为强连通分量(strongly connected components)。
下图中,子图{1,2,3,4}为一个强连通分量,因为顶点1,2,3,4两两可达。{5},{6}也分别是两个强连通分量。
Tarjan算法是基于对图深度优先搜索的算法,每个强连通分量为搜索树中的一棵子树。搜索时,把当前搜索树中未处理的节点加入一个堆栈,回溯时可以判断栈顶到栈中的节点是否为一个强连通分量。
定义DFN(u)为节点u搜索的次序编号(时间戳),Low(u)为u或u的子树能够追溯到的最早的栈中节点的次序号。由定义可以得出,
Low(u)=Min{DFN(u),Low(v),(u,v)为树枝边,u为v的父节点{
如果下一个要访问的V节点,没有访问过,则low[U]=min(low[u],low[v])。
DFN(v),(u,v)为指向栈中节点的后向边(非横叉边)}
{如果已经在栈中了,则low[U]=min(low[u],dfn[v])
当DFN(u)=Low(u)时,以u为根的搜索子树上所有节点是一个强连通分量。
依照上面红色字体的定义,只要当DFN[U]=LOW[U] 便说明此时栈中的元素,从元素U以上的栈中元素都属于同一个强连通分量。
下面给出TARJAN算法的具体代码:
#include
#include
struct
{
int x,y,n;
}e[1000];
int f[1000];
int o;
bool v[1000];
bool vs[1000];
int b[1000][1000],dfn[1000],low[1000],zhan=0,lian,ti=0;
int stick[1000];
int min(int a,int b)
{
return (a
}
void add(int a,int b)
{
o++;
e[o].x=a;
e[o].y=b;
e[o].n=f[a];
f[a]=o;
}
void tanzhan(int u)
{
lian++;
int j=1;
while (stick[zhan]!=u)
{
b[lian][j++]=stick[zhan--];
vs[stick[zhan]]=false;
}
b[lian][j++]=stick[zhan];{B数组记录的是每一个强连通分量,及每一个强连通分量中的元素}
vs[u]=false;
b[lian][0]=j-1;
}
void tarjan(int u)
{
dfn[u]=++ti;
if (low[u]==0) low[u]=dfn[u];
stick[++zhan]=u;
vs[u]=true;{VS表示当前元素是否在模拟的栈中}
int t=f[u];
v[u]=true;
while (e[t].y!=0)
{
if (v[e[t].y]==false)
{
tarjan(e[t].y);
low[u]=min(low[u],low[e[t].y]);{由上面红色字体的定义,更新每一个点的LOW值和DFN值}
}else
{
if (vs[e[t].y]==true)
low[u]=min(low[u],dfn[e[t].y]);
}
t=e[t].n;
}//这里没有回溯的过程,因此TARJAN是O(M+N)的算法。
if (dfn[u]==low[u]) tanzhan(u);
}
int main()
{
int m;
freopen("tarjan.in","r",stdin);
freopen("tarjan.out","w",stdout);
scanf("%d",&m);
for (int i=1;i<=m;i++)
{
int a,b;
scanf("%d%d",&a,&b);
add(a,b);
}
for (int i=1;i<=6;i++)
{
if (v[i]==false) tarjan(i);
}
for (int i=1;i<=6;i++)
{printf("%d\n",dfn[i]);
printf("%d\n",low[i]);}
return 0;
}
图中的元素采用模拟链表存储,其中需要注意的是,因为tarjan算法是只会对每一个图中的节点访问一遍,因此不需要回溯(即V数组,只会更新一遍)
{foreverzsz 原创,转载请注明出处.}