有向图的强连通分量(SCC)

来源:互联网 发布:漓江学院 知乎 编辑:程序博客网 时间:2024/06/05 08:08

     在有向图G 中,如果两个顶点间至少存在一条路径,称两个顶点强连通(strongly connected)。

     如果有向图G 的每两个顶点都强连通,称G 是一个强连通图。

     非强连通图有向图的极大强连通子图,称为强连通分量(strongly connected components)。

下图中,子图{1,2,3,4}为一个强连通分量,因为顶点1,2,3,4两两可达,{5},{6}也分别是两个强连通分量。



   直接根据定义,用双向遍历取交际的方法求强连通分量,时间复杂度为O(N^2+M)。更好的方法是Kosaraju算法或者Tarjan算法。

   两者的时间复杂度都是O(N+M)。

Tarjan 算法:

     Tarjan 算法是基于对图深度优先搜索的算法,每个强连通分量为搜索树中的一棵子树。搜索时,把当前搜索树中未处理的节点加入一个堆栈,回溯时可以判断栈顶到栈中的节点是否为一个强连通分量。
     定义DFN(u)为节点u 搜索的次序编号(时间戳),Low(u)为u 或u 的子树能够追溯到的最早的栈中节点的次序号。由定义可以得出,当 DFN(u)=Low(u)时,以u 为根的搜索子树上所有节点是一个强连通分量。

算法步骤:

 1.当首次搜索到点u时dfn[u]=low[n]=time

  2.每当搜索到一个点,把该点压入栈顶

  3.当u和v有边相连时,如果v不在栈中(树枝边),dfs(v),并且low[u]=min{low(u),low(v)},如果v在栈中(前向边/后向边),此时low[u]=min{low[u],dfn[v]}

  4.当dfn[u]=low[u]时,将它以及在它之上的元素弹出栈,此时,弹出栈的结点构成一个强连通分量

  5.继续搜索,知道图被遍历完毕。

由于在这个过程中每个点只被访问一次,每条边也只被访问一次,所以Tarjan算法的时间复杂度是O(n+m)


Tarjan模板:

#include <algorithm>#include <iostream>#include <cstring>#include <cstdio>#include <stack>#define max(a,b) (a>b?a:b)#define min(a,b) (a>b?b:a)using namespace std;const int N=1001;int time=1;int low[N],dfn[N];bool instack[N];stack<int>st;struct LIST{    int v;    LIST *next;};LIST *head[N]={NULL};void tarjan(int v)/*tarjan求强连通分支*/{    dfn[v]=low[v]=time++;/*标记点v的DFS遍历序号*/    st.push(v);/*将点v入栈*/    instack[v]=true;/*标记点v已经在栈中*/    for(LIST *p=head[v];p!=NULL;p=p->next)/*遍历V能直接到达的点*/    {        if(!dfn[p->v])/*如果v的邻接点没有入过栈*/        {            tarjan(p->v);            low[v]=min(low[v],low[p->v]);/*如果v能直接到达的这个点没在栈中,v的最早祖先为他们中的较小值*/        }        else if(instack[p->v])/*如果在栈中*/            low[v]=min(low[v],dfn[p->v]);/*如果在栈中,则v的最早祖先是他的序号和那个点的序号较小的*/    }    if(dfn[v]==low[v])/*如果dfn[v]和low[v]相等,则说明v点是其所属强连通分支DFS遍历起点,这个强连通分支所有点都在v点之上*/    {        cout<<"{ ";        do        {            v=st.top();            st.pop();            instack[v]=false;            cout<<v<<' ';        }while(dfn[v]!=low[v]);        cout<<"}"<<endl;    }}int main(){    int i,j,n,m;    cin>>n;    while(!st.empty())        st.pop();    memset(dfn,0,sizeof(dfn));    memset(instack,false,sizeof(instack));    for(i=0;i<=n;i++)        head[i]=NULL;    for(i=1;i<=n;i++)    {        cin>>m;//i的邻接点数量        //输入每个邻接点编号        LIST *rear=head[i];        for(j=0;j<m;j++)/*创建邻接表*/        {            if(!j)            {                rear=new LIST;                head[i]=rear;            }            else            {                rear->next=new LIST;                rear=rear->next;            }            rear->next=NULL;            cin>>rear->v;        }    }    for(i=1;i<=n;i++)        if(!dfn[i])/*如果i没有入过栈*/            tarjan(i);    return 0;}

例题:

hdu 1269 

   算是Tarjan 的模板题

   题解传送门:http://blog.csdn.net/ezcufst/article/details/47017459

0 0
原创粉丝点击