算法模板——Tarjan算法

来源:互联网 发布:广州淘宝代运营诈骗 编辑:程序博客网 时间:2024/06/05 11:48

Tarjan算法是一种用来求强连通分量的算法。
什么是强连通分量?
一个有向图的极大强连通子图叫做强连通分量。就是说,一个图的一个子图是强连通图,再加进一个点和与之相邻的边,那么这个图就不是强连通图,这样这个子图是原图的强连通分量。
确定一些名词
首先每张图都有一棵dfs树,树上的边叫树边
从这棵树的一个节点到自己的儿孙节点且不是树边的边叫做前向边
从这棵树的一个节点到自己的祖先节点的边叫做返祖边
从一个节点的一棵子树到另一棵子树的边叫做横叉边
如何确定一个图的强连通分量?
一个点i开始dfs时,记录这个点的dfni。记录一个lowi,代表i点能沿着返祖边和树边走到的dfs序最小的节点的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树的一棵子树。
首先处理返祖边,有返祖边说明从这个点的这个祖先到这个点自己这条链都是一个强连通分量。树边的话显然这个点的low是取当前点与儿子的low值中最低的那个。横叉边显然是不可以更新low值的,那么判断一下就好了。前向边显然不可以更新。这样就可以确定强连通分量了。
具体细节看代码好了。

代码

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;  }};