Tarjan算法是一种用来求强连通分量的算法。
什么是强连通分量?
一个有向图的极大强连通子图叫做强连通分量。就是说,一个图的一个子图是强连通图,再加进一个点和与之相邻的边,那么这个图就不是强连通图,这样这个子图是原图的强连通分量。
确定一些名词
首先每张图都有一棵dfs树,树上的边叫树边。
从这棵树的一个节点到自己的儿孙节点且不是树边的边叫做前向边。
从这棵树的一个节点到自己的祖先节点的边叫做返祖边。
从一个节点的一棵子树到另一棵子树的边叫做横叉边。
如何确定一个图的强连通分量?
一个点
i
开始dfs时,记录这个点的
伪代码
int tarjan(int u){
dfn[u]=low[u]=++cnt;
for(v=u的每一个儿子){
if(v没有被搜过){
tarjan(v);
low[u]=min(low[v],low[u]);
}
else if(v在栈内)
{
low[u]=min(dfn[v],low[u]);
}
}
if(dfn[u]==low[u])
{
将栈中u及u以上的点都弹出并标记为同一个强连通分量;
}
return 0;
}
为什么呢?
一个强连通分量必定是dfs树的一棵子树。
首先处理返祖边,有返祖边说明从这个点的这个祖先到这个点自己这条链都是一个强连通分量。树边的话显然这个点的
具体细节看代码好了。
代码
const int maxn=10000;
const int maxm=100000;
struct stack//普通的栈
{
int s[maxn+10],head;
int push(int x)
{
head++;
s[head]=x;
return 0;
}
int pop()
{
head--;
return 0;
}
int top()
{
return s[head];
}
int size()
{
return head;
}
};
struct ta
{
int pre[maxm+10],now[maxn+10],son[maxm+10],tot;//存图
int dfn[maxn+10],cnt,low[maxn+10],belong[maxn+10],b[maxn+10],total;
stack st;
int ins(int a,int b)//插入边
{
tot++;
pre[tot]=now[a];
now[a]=tot;
son[tot]=b;
return 0;
}
int tarjan(int u)
{
cnt++;
dfn[u]=low[u]=cnt;
int j=now[u];
st.push(u);//将这个点入栈
b[u]=1;//b数组标记是否在栈内
while(j)
{
int v=son[j];
if(!dfn[v])
{
tarjan(v);
if(low[v]<low[u])//处理树边
{
low[u]=low[v];
}
}
else
{
if((b[v])&&(dfn[v]<low[u]))//这里前一个条件处理了横叉边
{
low[u]=dfn[v];//这里处理前向边和返祖边
}
}
j=pre[j];
}
if(low[u]==dfn[u])//可以确定强连通分量了
{
total++;
do//将栈内的元素弹出
{
j=st.top();
belong[j]=total;
b[j]=0;
st.pop();
}
while(j!=u);
}
return 0;
}
};