强连通分量

强连通分量

在有向图中,如果存在两个点a bab间存在一条边且ba间也存在一条边,那么这两个点成为强连通。

如果一个图中个所有点都强连通,那么这个图叫强连通图。有向图的最大强连通子图称为强连通分量。

如图所示:{1 , 2 , 3 , 4 } , { 5 } ,  { 6 } 三个区域可以相互连通,称为这个图的强连通分量。

子图是指:选取V的一个子集V’,以及E当中所有满足u,v∈V’的边集E’所指代的图.

我们需要找出一幅子图的最大强连通分量:

最朴素的方法:

构造一个传递闭包(也就是数组Aij表示i能否到达j),然后把Aij=Aji=1的节点置于同一个强连通分量当中

这个算法的复杂度是O(n^3),优点是代码复杂度小,缺点是速度太慢了

由于这个方法太慢了,保管卡死你。所以我们这里用一个比较不错的方法

Kosaraju算法

原理和上面我们所说的是一样的,如果a能到达bb也能到达a,那么我们就说这两个点是强联通的。也可以等同于说,如果原图与逆图中A都能到达B,那么A与B在同一个强连通分量里面。

什么是逆图呢??

逆图,指的是将原本有向图的所有边的方向变成相反,也就是说原本从a到b的边变成从b到a。

我们只需要判断一下ab是否联通就ok了,但是又存在一个问题:我们要怎样求这些联通的点呢??

我们的这个算法有三步流程:

  1. 搜索整个图,判断整幅图所有节点的时间戳(离开这个节点是遍历了都少个点)。
  2. 对进行编号的节点选一个时间戳最大的节点,从这个节点开始遍历逆图,删除能遍历到的节点,这些节点构成一个强连通分量。
  3. 如果还有结点未删除,重复第二步。

这个方法的优点是实现起来十分简单

但这个地方我们有一个更好的方法:tarjian

Tarjan算法是一个通过对图进行深度优先搜索并通过同时维护一个栈以及两个相关时间戳的算法。 定义dfn(u)表示节点u搜索的次序编号(时间戳,第几个搜索的节点),low(u)表示节点u或u的子树当中可以找到的最早的栈中的节点的时间戳。

由定义,我们可以得到: Low(u)=dfn(u), low(u)=min(low(u),low(v))当(u,v)为搜索树当中的树枝边。 low(u)=min(low(u),dfn(v))当(u,v)为搜索树当中的后向边(也就是v仍在栈内的情况) 每当找到一个节点在遍历完后返回时low(u)=dfn(u),那么可以弹出栈中u以上的所有节点,此时这里的所有节点形成一个强连通分量

 

二.算法图示

 

以1为Tarjan 算法的起始点,如图

 

 

顺次DFS搜到节点6

 

 

 回溯时发现LOW[ 5 ]==DFN[ 5 ] ,  LOW[ 6 ]==DFN[ 6 ] ,则{ 5 } , { 6 } 为两个强连通分量。回溯至3节点,拓展节点4.

 

 

拓展节点1 , 发现1再栈中更新LOW[ 4 ],LOW[ 3 ] 的值为1

 

 

 回溯节点1,拓展节点2

 

 

自此,Tarjan Algorithm 结束,{1 , 2 , 3 , 4 } , { 5 } ,  { 6 } 为图中的三个强连通分量。

 

 

不难发现,Tarjan Algorithm 的时间复杂度为O(E+V).

 

来看看代码?!

 

void Tarjan ( int x ) {
         dfn[ x ] = ++dfs_num ;
         low[ x ] = dfs_num ;
         vis [ x ] = true ;//是否在栈中
         stack [ ++top ] = x ;
         for ( int i=head[ x ] ; i!=0 ; i=e[i].next ){
                  int temp = e[ i ].to ;
                  if ( !dfn[ temp ] ){
                           Tarjan ( temp ) ;
                           low[ x ] = gmin ( low[ x ] , low[ temp ] ) ;
                 }
                 else if ( vis[ temp ])low[ x ] = gmin ( low[ x ] , dfn[ temp ] ) ;
         }
         if ( dfn[ x ]==low[ x ] ) {//构成强连通分量
                  vis[ x ] = false ;
                  color[ x ] = ++col_num ;//染色
                  while ( stack[ top ] != x ) {//清空
                           color [stack[ top ]] = col_num ;
                           vis [ stack[ top-- ] ] = false ;
                 }
                 top -- ;
         }
}

 

 

改编于:http://www.cnblogs.com/shadowland/p/5872257.html

转载于:https://www.cnblogs.com/z360/p/7010712.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值