作用
Tarjan算法可以将强连通分量合并到该强连通分量的某一个点上,通俗地说就是实现环缩点。
有向图的强连通分量
有向图中,如果两个点i,j之间既有i->j的路径,又有j->i的路径,则i,j强连通。所有点都强连通的图是强连通图,一个有向图中的所有最大(就是不能再扩大了)的强连通子图都是这个有向图的强连通分量。
有时候我们不需要考虑强连通分量中的点,而是需要直接考虑整个强连通分量。这就需要我们找出图中的强连通分量,Tarjan是不错的选择。
实现
我们记录时间戳ti,然后为每个点都记录dfn[i](i的最早访问时间)和low[i](i能追溯到的最早时间),再开一个栈stk表示目前处理的强连通分量中的点。访问一个点i时,先dfn[i]=low[i]=++ti,然后从i开始遍历,假设目前处理的i的儿子是son,那么分为两种情况:
1.son还没处理过,那么就递归调用Tarjan(son),先把son[j]处理出来,然后追溯到low[son],所以low[i]=min(low[i],low[son])。
2.son已经处理过了,还要分两种情况:
(1)son不在栈中,说明son是其他强连通分量中的点,故不处理。
(2)son在栈中,说明son是目前处理的强连通分量中的点,那么就可以被i追溯,所以low[i]=min(low[i],dfn[son])。
一个坑:这里到底是low[son]还是dfn[son]会感觉傻傻分不清,好像如果只求强连通分量效果是相同的,但由于Tarjan还可以求双连通分量,所以dfn[son]才是符合定义的,但是我并不会双连通分量啊QAQ!所以会了再来填坑吧。
模板
void Tarjan(int x)
{
dfn[x]=low[x]=++ti;instk[x]=true;stk[++top]=x;
for (int j=lnk[x];j;j=nxt[j]) //这里用邻接表存图
if (!dfn[son[j]]) Tarjan(son[j]),low[x]=min(low[x],low[son[j]]); else
if (instk[son[j]]) low[x]=min(low[x],dfn[son[j]]);
int y;
if (dfn[x]==low[x]) do //将栈中所有节点并入x
{
y=stk[top--];instk[y]=false;
father[y]=x;num[x]++; //father表示父亲,num表示节点个数
} while (x!=y);
}