有向图的强连通分量

有向图强连通分量(SCC) 有向图G 中,如果两个顶点间至少存在一条路径,称两个顶点连通(strongly connected)。 如果有向图G 的每两个顶点都连通,称G 是一个连通图。 非连通有向图的极大连通子图,称为强连通分量(strongly connected components)。 下图中,子图{1,2,3,4}为一个强连通分量,因为顶点1,2,3,4两两可达,{5},{6}也分别是两 阅读详情

最关键通用部分:强连通分量一定是图的深搜树的一个子树。

 

一、     Kosaraju算法

1.      算法思路

基本思路:

这个算法可以说是最容易理解,最通用的算法,其比较关键的部分是同时应用了原图G和反图GT(步骤1)先用对原图G进行深搜形成森林(),(步骤2)然后任选一棵树对其进行深搜(注意这次深搜节点A能往子节点B走的要求是EAB存在于反图GT),能遍历到的顶点就是一个强连通分量。余下部分和原来的森林一起组成一个新的森林,继续步骤2直到 没有顶点为止。

改进思路:

当然,基本思路实现起来是比较麻烦的(因为步骤2每次对一棵树进行深搜时,可能深搜到其他树上去,这是不允许的,强连通分量只能存在单棵树中(由开篇第一句话可知)),我们当然不这么做,我们可以巧妙的选择第二深搜选择的树的顺序,使其不可能深搜到其他树上去。想象一下,如果步骤2是从森林里选择树,那么哪个树是不连通(对于GT来说)到其他树上的呢?就是最后遍历出来的树,它的根节点在步骤1的遍历中离开时间最晚,而且可知它也是该树中离开时间最晚的那个节点。这给我们提供了很好的选择,在第一次深搜遍历时,记录时间i离开的顶点j,即numb[i]=j。那么,我们每次只需找到没有找过的顶点中具有最晚离开时间的顶点直接深搜(对于GT来说)就可以了。每次深搜都得到一个强连通分量。 

隐藏性质:

    分 析到这里,我们已经知道怎么求强连通分量了。但是,大家有没有注意到我们在第二次深搜选择树的顺序有一个特点呢?如果在看上述思路的时候,你的脑子在思 考,相信你已经知道了!!!它就是:如果我们把求出来的每个强连通分量收缩成一个点,并且用求出每个强连通分量的顺序来标记收缩后的节点,那么这个顺序其 实就是强连通分量收缩成点后形成的有向无环图的拓扑序列。为什么呢?首先,应该明确搜索后的图一定是有向无环图呢?废话,如果还有环,那么环上的顶点对应 的所有原来图上的顶点构成一个强连通分量,而不是构成环上那么多点对应的独自的强连通分量了。然后就是为什么是拓扑序列,我们在改进分析的时候,不是先选 的树不会连通到其他树上(对于反图GT来说),也就是后选的树没有连通到先选的树,也即先出现的强连通分量收缩的点只能指向后出现的强连通分量收缩的点。那么拓扑序列不是理所当然的吗?这就是Kosaraju算法的一个隐藏性质。 

2.      伪代码

Kosaraju_Algorithm:

 step1:对原图G进行深度优先遍历,记录每个节点的离开时间。

 step2:选择具有最晚离开时间的顶点,对反图GT进行遍历,删除能够遍历到的顶点,这些顶点构成一个强连通分量。

 step3:如果还有顶点没有删除,继续step2,否则算法结束。 

3.      实现代码:

#include <iostream>

using namespace std;

 

const int MAXN = 110;

 

typedef int AdjTable[MAXN]; //邻接表类型

 

int      n;

bool     flag[MAXN]; //访问标志数组

int      belg[MAXN]; //存储强连通分量,其中belg[i]表示顶点i属于第belg[i]个强连通分量

int      numb[MAXN]; //结束时间标记,其中numb[i]表示离开时间为i的顶点

AdjTable adj[MAXN], radj[MAXN]; //邻接表,逆邻接表

 

//用于第一次深搜,求得numb[1..n]的值

void VisitOne(int cur, int &sig)

{

  flag[cur] = true;

 

  for ( int i=1; i<=adj[cur][0]; ++i )

  {

     if ( false==flag[adj[cur][i]] )

     {

         VisitOne(adj[cur][i],sig);

     }

  }

 

  numb[++sig] = cur;

}

 

//用于第二次深搜,求得belg[1..n]的值

void VisitTwo(int cur, int sig)

{

  flag[cur] = true;

  belg[cur] = sig;

 

  for ( int i=1; i<=radj[cur][0]; ++i )

  {

     if ( false==flag[radj[cur][i]] )

     {

         VisitTwo(radj[cur][i],sig);

     }

  }

}

 

//Kosaraju算法,返回为强连通分量个数

int Kosaraju_StronglyConnectedComponent()

{

  int  i, sig;

 

  //第一次深搜

  memset(flag+1,0,sizeof(bool)*n);

  for ( sig=0,i=1; i<=n; ++i )

  {

     if ( false==flag[i] )

     {

         VisitOne(i,sig);

     }

  }

 

  //第二次深搜

  memset(flag+1,0,sizeof(bool)*n);

  for ( sig=0,i=n; i>0; --i )

  {

     if ( false==flag[numb[i]] )

     {

         VisitTwo(numb[i],++sig);

     }

  }

 

  return sig;  

}

 

二、     Trajan算法

1.      算法思路:

这 个算法思路不难理解,由开篇第一句话可知,任何一个强连通分量,必定是对原图的深度优先搜索树的子树。那么其实,我们只要确定每个强连通分量的子树的根, 然后根据这些根从树的最低层开始,一个一个的拿出强连通分量即可。那么身下的问题就只剩下如何确定强连通分量的根和如何从最低层开始拿出强连通分量了。

那么如何确定强连通分量的根,在这里我们维护两个数组,一个是indx[1..n],一个是mlik[1..n],其中indx[i]表示顶点i开始访问时间,mlik[i]为与顶点i邻接的顶点未删除顶点jmlik[j]mlik[i]的最小值(mlik[i]初始化为indx[i])。这样,在一次深搜的回溯过程中,如果发现mlik[i]==indx[i]那么,当前顶点就是一个强连通分量的根,为什么呢?因为如果它不是强连通分量的跟,那么它一定是属于另一个强连通分量,而且它的根是当前顶点的祖宗,那么存在包含当前顶点的到其祖宗的回路,可知mlik[i]一定被更改为一个比indx[i]更小的值。

至于如何拿出强连通分量,这个其实很简单,如果当前节点为一个强连通分量的根,那么它的强连通分量一定是以该根为根节点的(剩下节点)子 树。在深度优先遍历的时候维护一个堆栈,每次访问一个新节点,就压入堆栈。现在知道如何拿出了强连通分量了吧?是的,因为这个强连通分量时最先被压人堆栈 的,那么当前节点以后压入堆栈的并且仍在堆栈中的节点都属于这个强连通分量。当然有人会问真的吗?假设在当前节点压入堆栈以后压入并且还存在,同时它不属 于该强连通分量,那么它一定属于另一个强连通分量,但当前节点是它的根的祖宗,那么这个强连通分量应该在此之前已经被拿出。现在没有疑问了吧,那么算法介 绍就完了。 

2.      伪代码:

Tarjan_Algorithm:

   step1:

   找一个没有被访问过的节点vgoto step2(v)。否则,算法结束。

      step2(v):

       初始化indx[v]mlik[v]

       对于v所有的邻接顶点u

              1)     如果没有访问过,则step2(u),同时维护mlik[v]

              2)     如果访问过,但没有删除,维护mlik[v]

       如果indx[v]==mlik[v],那么输出相应的强连通分量 

3.      实现代码

#include <iostream>

using namespace std;

 

const int  MAXN    = 110;

const char NOTVIS  = 0x00;   //顶点没有访问过的状态

const char VIS     = 0x01;   //顶点访问过,但没有删除的状态

const char OVER    = 0x02;   //顶点删除的状态

 

typedef int AdjTable[MAXN];  //邻接表类型

 

int      n;

char     flag[MAXN];         //用于标记顶点状态,状态有NOTVIS,VIS,OVER

int      belg[MAXN];         //存储强连通分量,其中belg[i]表示顶点i属于第belg[i]个强连通分量

int      stck[MAXN];         //堆栈,辅助作用

int      mlik[MAXN];         //很关键,与其邻接但未删除顶点地最小访问时间

int      indx[MAXN];         //顶点访问时间

AdjTable adj[MAXN];          //邻接表

 

//深搜过程,该算法的主体都在这里

void Visit(int cur, int &sig, int &scc_num)

{

   int  i;

 

   stck[++stck[0]] = cur; flag[cur] = VIS;

   mlik[cur] = indx[cur] = ++sig;

 

   for ( i=1; i<=adj[cur][0]; ++i )

   {

      if ( NOTVIS==flag[adj[cur][i]] )

      {

          Visit(adj[cur][i],sig,scc_num);

          if ( mlik[cur]>mlik[adj[cur][i]] )

          {

             mlik[cur] = mlik[adj[cur][i]];

          }

      }

      else if ( VIS==flag[adj[cur][i]] )

      {

          if ( mlik[cur]>indx[adj[cur][i]] )  //该部分的indx应该是mlik,但是根据算法的属性,使用indx也可以,且时间更少

          {

             mlik[cur] = indx[adj[cur][i]]; 

          }

      }

   }

 

   if ( mlik[cur]==indx[cur] )

   {

      ++ scc_num;

      do

      {

          belg[stck[stck[0]]] = scc_num;

          flag[stck[stck[0]]] = OVER;

      }

      while ( stck[stck[0]--]!=cur );   

   }

}

 

//Tarjan算法,求解belg[1..n],且返回强连通分量个数,

int Tarjan_StronglyConnectedComponent()

{

   int  i, sig, scc_num;

 

   memset(flag+1,NOTVIS,sizeof(char)*n);

 

   sig = 0; scc_num = 0; stck[0] = 0;

   for ( i=1; i<=n; ++i )

   {

      if ( NOTVIS==flag[i] )

      {

          Visit(i,sig,scc_num);

      }

   }

 

   return scc_num;

} 

 

三、   Gabow算法

1.      思路分析

这个算法其实就是Tarjan算法的变异体,我们观察一下,只是它用第二个堆栈来辅助求出强连通分量的根,而不是Tarjan算法里面的indx[]mlik[]数组。那么,我们说一下如何使用第二个堆栈来辅助求出强连通分量的根。

我们使用类比方法,在Tarjan算法中,每次mlik[i]的修改都是由于环的出现(不然,mlik[i]的值不可能变小),每次出现环,在这个环里面只剩下一个mlik[i]没有被改变(深度最低的那个),或者全部被改变,因为那个深度最低的节点在另一个环内。那么Gabow算 法中的第二堆栈变化就是删除构成环的节点,只剩深度最低的节点,或者全部删除,这个过程是通过出栈来实现,因为深度最低的那个顶点一定比前面的先访问,那 么只要出栈一直到栈顶那个顶点的访问时间不大于深度最低的那个顶点。其中每个被弹出的节点属于同一个强连通分量。那有人会问:为什么弹出的都是同一个强连 通分量?因为在这个节点访问之前,能够构成强连通分量的那些节点已经被弹出了,这个对Tarjan算法有了解的都应该清楚,那么Tarjan算法中的判断根我们用什么来代替呢?想想,其实就是看看第二个堆栈的顶元素是不是当前顶点就可以了。

现在,你应该明白其实Tarjan算法和Gabow算法其实是同一个思想的不同实现,但是,Gabow算法更精妙,时间更少(不用频繁更新mlik[]) 

2.      伪代码

Gabow_Algorithm:

           step1:

     找一个没有被访问过的节点vgoto step2(v)。否则,算法结束。

           step2(v):

            v压入堆栈stk1[]stk2[]

            对于v所有的邻接顶点u

  1)     如果没有访问过,则step2(u)

  2)     如果访问过,但没有删除,维护stk2[](处理环的过程)

            如果stk2[]的顶元素==v,那么输出相应的强连通分量 

3.      实现代码

#include <iostream>

using namespace std;

 

const int MAXN = 110;

 

typedef int AdjTable[MAXN]; //邻接表类型

 

int      n;

int      intm[MAXN]; //标记进入顶点时间

int      belg[MAXN]; //存储强连通分量,其中belg[i]表示顶点i属于第belg[i]个强连通分量

int      stk1[MAXN]; //辅助堆栈

int      stk2[MAXN]; //辅助堆栈

AdjTable adj[MAXN]; //邻接表

 

//深搜过程,该算法的主体都在这里

void Visit(int cur, int &sig, int &scc_num)

{

   int  i;

 

   intm[cur] = ++sig;

   stk1[++stk1[0]] = cur;

   stk2[++stk2[0]] = cur;

 

   for ( i=1; i<=adj[cur][0]; ++i )

   {

      if ( 0==intm[adj[cur][i]] )

      {

          Visit(adj[cur][i],sig,scc_num);

      }

      else if ( 0==belg[adj[cur][i]] )

      {

          while ( intm[stk2[stk2[0]]]>intm[adj[cur][i]] )

          {

             -- stk2[0];

          }

      }

   }

 

   if ( stk2[stk2[0]]==cur )

   {

      -- stk2[0]; ++ scc_num;

      do

      {

          belg[stk1[stk1[0]]] = scc_num;

      }

      while ( stk1[stk1[0]--]!=cur );

   }  

}

 

//Gabow算法,求解belg[1..n],且返回强连通分量个数,

int Gabow_StronglyConnectedComponent()

{

   int  i, sig, scc_num;

 

   memset(belg+1,0,sizeof(int)*n);

   memset(intm+1,0,sizeof(int)*n);

   sig = 0; scc_num = 0; stk1[0] = 0; stk2[0] = 0; 

   for ( i=1; i<=n; ++i )

   {

      if ( 0==intm[i] )

      {

          Visit(i,sig,scc_num);

      }

   }

 

   return scc_num;

} 

 

四、  总结

    写到这里,做一个总结:Kosaraju算法的第二次深搜隐藏了一个拓扑性质,而Tarjan算法和Gabow算法省略了第二次深搜,所以,它们不具有拓扑性质。Tarjan算法用堆栈和标记,Gabow用两个堆栈(其中一个堆栈的实质是代替了Tarjan算法的标记部分)来代替Kosaraju算法的第二次深搜,所以只用一次深搜,效率比Kosaraju算法高。

图论——强连通分量详解! 强连通分量等概念Tarjan算法的过程与实现就讲这么多,平时练习多注意vector与链式前向星的转换。今宵东方不见日,总有夜尽天明时。加油拜拜~~~(特意标红。。。 阅读详情

相关推荐

有向图强连通分量

文章目录有向图强连通分量1 基本概念1.1 名词解释1.2 重要性质1.3 结论2. 板子3. 例题3.1 tarjan + 缩点 + 度3.2 tarjan + 缩点 + dp3.2.1 求最长链、求方案数3.2.2 求解差分约束3.2.3 求解必经点问题 有向图强连通分量 1 基本概念 1.1 名词解释 强连通分量:如果有向图中任意两点都有互相可达的路径,则此图为连通图。有向图G的极大连通子图称为G的强连通分量(SCC)(单点肯定都是scc,但要使scc尽可能大,所以能大尽量大) dfn[x]数组:

m0_49959202的博客 921

图论算法——有向图中的连通

引言 本文我们着重分析下有向图连通性以及其应用。 连通 在一幅无向图中,如果有一条路径连接顶点v和w,则它们就是连通的;然后,在一幅有向图中,如果从顶点v有一条有向路径达到w,则顶点w是从顶点v可达的,但如果从w到达v的路径可能不存在。这两个顶点不是连通的。 如果两个顶点互相可达,则它们是连通的。如果一幅有向图中任意两个顶点都是连通的,则这幅有向图也是连通的。 强连通分量 有向图...

日积月累,天道酬勤 2万+

强连通分量(SCC, Strongly Connected Components)

强连通分量及其Tarjan算法

Dong_HFUT的博客 9789

判断连通

本总结是是个人为防止遗忘而作,不得转载和商用。 什么是连通图          对一个有向图,如果每个节点都存在到达其他任何节点的路径,那么就称它是连通的。 如何判断连通图          任取有向图G的某结点S,从S开始进行深度优先搜索,若可以遍历G的所有结点,则将G的所有边反向,再次从S开始进行深度优先搜索,如果再次能够遍历G的所有结点,则G是连通图,两次搜索有一次无法遍历所

xueyingxue001的专栏 2万+

强连通分量(tarjan算法

连通:两个点u,v可以互相到达强连通分量,一个图中每一块的任意点可以互相到达的数量(不一定整个图连通,但是局部连通

WQhuanm的博客 3749

强连通分量个数的求法(图解)

强连通分量个数的求法(图解) 背景 最近刷软考题的时候,碰到2013年上半年软件设计师的第31题,求程序图的环路复杂度。答案解析中有这么一段话: 根据图论,在一个连通有向图G中,环的个数V(G)由以下公式给出: V(G)=m−n+2p(∗)V(G)=m-n+2p(*)V(G)=m−n+2p(∗) 其中,V(G)是有向图G中的环路数,m是图G中弧的个数,n是图G中的结点数,p是图G中的强连通分量个数。 因为一直习惯了用V=m-n+2的公式计算,所以刚看到(*)公式时有点反应不过来,算是自己学习上的一个

weixin_38327135的博客 2万+

有向图强连通分量算法

强连通分量 Tarjan算法, Kosaraju 算法

Jasonchen1224的博客 4749

有向图——强连通分量

有向图强连通分量(strongly connected components) 在有向图G中,如果两个顶点vi,vj间(vi!=vj)有一条从vi到vj的路径,同时还有一条从vj到vi的路径(顶点相互可达),则称两个顶点连通。如果有向图G的每对顶点都连通,称G是个连通图。非连通有向图的极大连通子图,称为强连通分量。     求解强连通分量算法主要有三种:Kosaraju

xyjikl 743

有向图强连通分量(Tarjian)

强连通分量 有向图强连通分量:在有向图G中,如果两个顶点vi,vj间(vi>vj)有一条从vi到vj的有向路径,同时还有一条从vj到vi的有向路径,则称两个顶点连通(strongly connected)。如果有向图G的每两个顶点都连通,称G是一个连通图。有向图的极大连通子图,称为强连通分量(strongly connected components)。 下图中,子图{1,2,3,...

Stormjing的博客 3919

【图论】—— 有向图强连通分量

【图论】—— 有向图强连通分量

forever_bryant的博客 3478

有向图强连通分量(SCC)

有向图强连通分量(SCC) 1. 有向图强连通分量原理 原理 强连通分量是针对有向图来说的。如下的讲解默认都是针对有向图的。 连通分量:对于一个有向图中的一些点来说,如果任意两点都能相互到达,则称这些点以及对应的边构成连通分量。 强连通分量:指极大连通分量。即该联通分量中增加任何一个点都不能构成连通分量了。 那么强连通分量有什么作用呢? 我们可以通过使用求解联通分量的方式将一个有向图缩点成有向无环图(DAG),也称为拓扑图。缩点:指将联通分量缩成一个点。 转化为拓扑图有什么好处

weixin_42638946的博客 4404

强连通分量

强连通分量学习

qq_54783066的博客 1792

有向图强连通分量(一)

有向图强连通分量

L1zMaking的博客 1263

图论学习-有向图强连通分量

文章目录有向图强连通分量1.定义:2.基本术语与概念2.1 边的概念2.2 缩点2.3 时间戳3. tarjan求强连通分量(SCC)3.1 原理3.2 步骤3.3 模板3.3.1 tarjan求强连通分量的过程3.3.2 缩点的过程4.例题题目1:P2341 [USACO03FALL][HAOI2006]受欢迎的牛 G题目2:5.参考资料 有向图强连通分量 1.定义: 给定一张有向图,若对于图中任意两个节点x,y,既存在从x到y的路径,也存在y到x的路径,则该有向图连通图。 有向图的极大连通子图被称

麒麟001号的博客 2451
上一篇: C++中文件的读写
下一篇: 对等网络(P2P)的概念与思想
crfoxzl
博客等级 码龄19年 98粉丝 80原创
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值