强连通分量与双连通分量(重新构图)

强连通分量:

对于有向连通图,如果任意两点之间都能到达,则称为强连通图。如果对于有向图的一个子图是强连通的,则称为强连通子图;

极大的强连通子图称为强连通分量。一个有向图可以有多个强连通分量,一个点也算是强连通分量。强连通分量的术语是strongly connnected components,简称SCC

 

tarjan算法的基础是深度优先搜索,用两个数组low和dfn,和一个栈。low数组是一个标记数组,记录该点所在的强连通子图所在搜索子树的根节点的dfn值,dfn数组记录搜索到该点的时间,也就是第几个搜索这个点的。根据以下几条规则,经过搜索遍历该图和对栈的操作,我们就可以得到该有向图的强连通分量。
 
算法规则:
 1.数组的初始化:当首次搜索到点p时,Dfn与Low数组的值都为到该点的时间。
 2.堆栈:每搜索到一个点,将它压入栈顶。
 3.当点p有与点p’相连时,如果此时(时间为dfn[p]时)p’不在栈中,p的low值为两点的low值中较小的一个。
 4.当点p有与点p’相连时,如果此时(时间为dfn[p]时)p’在栈中,p的low值为p的low值和p’的dfn值中较小的一个。
5.每当搜索到一个点经过以上操作后(也就是子树已经全部遍历)的low值等于dfn值,则将它以及在它之上的元素弹出栈。这些出栈的元素组成一个强连通分量。 
6.继续搜索(或许会更换搜索的起点,因为整个有向图可能分为两个不连通的部分),直到所有点被遍历。
算法伪代码:


tarjan(u) { 
  DFN[u]=Low[u]=++Index       // 为节点u设定次序编号和Low初值   Stack.push(u)                   // 将节点u压入 栈中   for each (u, v) in E              // 枚举每一条边     if (!dfn[v])          // 如果节点v未被访问过 
{ 
      tarjan(v)               // 继续向下找      
      Low[u] = min(Low[u], Low[v]) 
} 
    else if (v in S)             // 如果节点v还在栈内       
    Low[u] = min(Low[u], DFN[v]) 
  if (DFN[u] == Low[u])        // 如果节点u是强连通分量的根 
do{ 
      v = S.pop            // 将v退栈,为该强连通分量中一个顶点     }while(u == v); }

应用例子:(OJ 1484 popular cows) ( 值得深入学习一下,包括重新建图
题意:有N只牛,输入a,b的话,则说明b关注a,而且关注有传递性。例如c关注b,且b关注a,则c也关注a。题目问有多少只奶牛能被其他所有的奶牛关注。
把题目的模型转换:N个顶点的有向图,有M条边。求一共有多少个点,满足这样的条件:所有其它的点都可以到达这个点。这个点满足条件的充要条件是:这个点是树中唯一的出度为0的点。
 
先求强连通分量,然后可以把强连通分量缩成一个点,因为,在强连通分量中的任意两个点可以到达,所有的点具有相同的性质,即它们分别能到达的点集都是相同的,能够到达它们的点集也是相同的。然后就重新构图,缩点后的图是没有强连通分量的,否则,可将环上所有点也缩成一个点,与极大强联通分量矛盾。所以只要找出度为0的点,并且出度为0的点只有1个 ,如果出度为0的点有多个的话,问题则无解。  代码:


#include<cstdio> 
#include<cstring>
using namespace std; 
const int adj=10010; 
const int  edg=50010 
struct node 
{ 
    int v;
    int next; 
};
node edge[edg];
node edge1[edg];
int  low[adj],dfn[adj],Stack[adj];
int  first[adj],first1[adj],fuck[adj];
bool  ins[adj];
int  n,m,temp,cnt,top,count;
int cnt_size[adj],belong[adj],outdegree[adj];
void creat(int u,int v)                 
{  
    edge1[count].next=first1[u];
    edge1[count].v=v;
    first1[u]=count++;
     
}  
void tarjan(int u) 
{
      int i,v;
      dfn[u]=low[u]=++temp;
      Stack[top++]=u;
      ins[u]=true;
      for(i=first[u]; i!=-1; i=edge[i].next) 
     {
           v=edge[i].v;
           if(!dfn[v]) 
         {
                
            tarjan(v);   
            if(low[u]>low[v])     
                low[u]=low[v];
               
        }   
        else if(ins[v])  
        {
            if(low[u]>dfn[v])    
                low[u]=dfn[v];
        }  
    }  
    if(dfn[u]==low[u])  
    {
        int j;  
        do   
        {
             top--;
             j=Stack[top];               
             ins[j]=false;  
             cnt_size[cnt]++;   
             belong[j]=cnt;               
        }
        while(u!=j);
        cnt++; 
    } 
}  
int main()
{  
    int i,j,k,v,sum,num;
    int e=0;
    scanf("%d%d",&n,&m);
    memset(first,-1,sizeof(first));
    for(k=0; k<m; k++)                
    {   
        scanf("%d%d",&i,&j); 
        edge[e].v=j;   
        edge[e].next=first[i];           
        first[i]=e;
        e++;          
    }  
    memset(dfn,0,sizeof(dfn));
    memset(ins,false,sizeof(ins));
    temp=cnt=top=0;
    memset(cnt_size,0,sizeof(cnt_size));
    memset(low,0,sizeof(low));
    for(i=1; i<=n; i++)              //求强连通分量 
    {   
        if(!dfn[i])    
            tarjan(i);  
    } 
    memset(first1,-1,sizeof(first1));
    count=0; 
    for(i=1; i<=n; i++)              //重新构造图  
    {
        for(j=first[i]; j!=-1; j=edge[j].next)   
        {
            v=edge[j].v;  
            if(belong[i]!=belong[v])     
                creat(belong[i],belong[v]);
                   
        } 
             
    } 
    memset(outdegree,0,sizeof(outdegree));
    for(i=0; i<cnt; i++)                 //求每个节点的出度  
    {
        for(j=first1[i]; j!=-1; j=edge1[j].next)    
            outdegree[i]++;
  
    } 
    sum=num=0;
 
    for(i=0; i<cnt; i++) 
    {
        if(outdegree[i]==0)           //求节点为0的个数  
         {    
            sum=cnt_size[i];
             num++;    
        }  
    } 
    if(num==1)   
        printf("%d\n",sum);
    else  
        printf("0\n");
     return 0;
     
}

加一段有注释的代码:

vector<int>g[N];  
int n,m;  
/* 
*instack记录节点u是否在栈中 
*dfn记录节点u第一次被访问是的时间戳 
*low记录与节点u和u的子树节点中最早的时间戳 
*belong记录每个节点属于的强连通分量编号 
*/  
int instack[N],low[N],dfn[N],stack[N],belong[N];  
int times,top,cnt;  
  
void tarjan(int u)  
{  
    int v;  
    dfn[u] = low[u] = ++times;  
    instack[u] = 1;  
    stack[++top] = u;  
    for(int i = 0 ; i < g[u].size() ; i ++)  
    {  
        v = g[u][i];  
        if(!dfn[v])//节点v还没有被访问  
        {  
            tarjan(v);//递归访问每个子树  
            low[u] = min(low[u],low[v]);//更新每个子树中最早访问的时间戳  
        }  
        //u与v相连,但v已经被访问过了,且还在栈中  
        //更新子树节点最早的时间戳  
        else if(instack[v] && dfn[v] < low[u])  
            low[u] = dfn[v];  
    }  
    //第一访问的时间戳和最早时间戳相等,说明节点u是强连通分量的根  
    if(dfn[u] == low[u])  
    {  
        cnt++;  
        do  
        {  
            v = stack[top--];  
            instack[v] = 0;  
            belong[v] = cnt;  
            cout<<v<<" ";  
        }  
        while(u != v);  
        cout<<endl;  
    }  
}


 双联通分量:

对于无向连通图,如果任意两点之间都有多于一条的路径,则称为双连通图。对于无向图的一个子图是双连通的,则称为双连通子图;

极大的双连通子图称为双连通分量。一个无向图可以有多个双连通分量,一个点也算是双连通分量。双连通分量的术语是biconnected components,简称为BCC

下面是双连通分量的模板代码(转载):

//----------DCC------------------

int dfn[MAXNODE],low[MAXNODE],index;//dfn记录各点被访问次序,low是追溯到DCC的根节点
//的dfn的值,当根节点的某个直接儿子节点的low值大于或等于根节点的dfn的值时,就可以从
//栈中取值了,直到取到根节点为止时一个DCC

int stack[MAXNODE],top;//栈:用深搜搜索节点并依次存储各个节点—以便于找到DCC(即当
//发现环时就是一个DCC,用low标记的,从该栈中取值取到该根节点为止)

int id_dcc[MAXNODE],cnt_dcc;//id_dcc:名副其实即DCC的id(编号),存储各节点的所在的
//编号(就是你给他们编的号,从1-n)cnt_dcc就是编号下标!

int father[MAXNODE];//由于求DCC是在一个无向连通图中,即为双向的图,该father就是为了
//防止某一节点又访问上一个节点(上一个节点搜出该节点)

void DFS_DCC(int cur)
{
    int next;                      //next为cur节点下的节点
     dfn[cur]=low[cur]=++index;
    stack[++top]=cur;
    for(Node *p=G[cur];p;p=p->next)
    {
        next=p->num;
        if(!dfn[next])
        {
            father[next]=cur;      //额,可以不用。。
              DFS_DCC(next);
            if(low[next]<low[cur]) //更新low使每一个DCC中的low的值==根节点low值
                low[cur]=low[next];
            if(low[next]>=dfn[cur])//当发现节点cur的儿子节点next的low值>=dfn[cur]
            {                      //则就要取栈,即是时候取出DCC了。为什么?因为不
                cnt_dcc++;           //这样就不对了^_^!(具体原因,自己举几个例子try)
                do
              {
                 next=stack[top--];
                 id_dcc[next]=cnt_dcc;
              }while(next!=cur);
              top++;               //这里为什么要++因为连着DCC是有共同节点的(举例try)
                                        //不++肯定要出错!也许我这code跟别人不一样,其实
                                       //思想都一样,只是具体实现的code有小小的差异罢了
            }
        }
        else if(next!=father[cur] && dfn[next]<low[cur])
        {
                                  //呃呃呃!其实不用这个father数组都可以了,只需
                                      //dfn[next]<low[cur]就行了 
            low[cur]=dfn[next];
        }            
    }
}
void solve()
{
    index=top=cnt_dcc=0;
    memset(dfn,0,sizeof(dfn));//初始化各下标及dfn
    DFS_DCC(1);//由于是无向连通图,只需深搜一个节点就都可以都到了
}


  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值