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

打印 上一主题 下一主题

主题 633|帖子 633|积分 1899



给定有向图 ,若存在 ,满足从 出发能到达 中所有的点,则称 是一个“流图”Flow Graph ),记为  ,其中, 称为流图的源点
在一个流图  上从  进行深度优先遍历,每个点只访问一次。所有发生递归的边  (换言之,从  到  是对  的第一次访问)构成一棵以  为根的树,我们把它称为流图  的搜索树
同时,在深度优先遍历的过程中,按照每一个节点第一次被访问的时间顺序,依次给予流图中 N 个节点 1~N 的整数标记,称为时间戳,记为 
   流图中的每条有向边  必然是以下四种之一:
  

  • 树枝边,指搜索树中的边,即  是  的父节点
  • 前向边,指搜索树中  是  的祖宗节点
  • 后向边,指搜索树中  是  的祖宗节点
  • 横叉边,指除了以上三种情况之外的边,它一定满足  
如下图“流图”以及其搜索树所示: 
加粗的表示的是树枝边,并构成一棵搜索树。 


有向图的强连通分量 

    给定一张有向图。若对于图中的任意两个结点 ,既存在从  到  的路径,也存在从  到  的路径,则称该有向图是“强连通图”
 
  有向图的极大连通子图称为“强连通分量”,简记为 SCC(Strongly Connected Component)。
  此处的“极大”的含义和双连通分量的“极大”的含义类似。
  Tarjan算法基于有向图的深度优先遍历,能够在线性的时间里求出一张有向图的强连通分量。
一个“环”一定是强连通图。如果既存在从  到 的路径, 也存在从  到  的路径,那么  显然在一个环中。因此,Tarjan算法的基本思路就是对每个点,尽量找到与它一起能够构成环的所有节点。
容易发现,“前向边”  没有什么用处,因为搜索树上本来就存在 从  到 的路径。
“后向边”  非常有用,因为它可以从搜索树上 从  到  的路径一起构成环。
“横向边”  视情况而定,如果从  出发能够找到一条回到  的祖宗节点,那么  就是有用的。

为了找到通过“后向边”和“横叉边”构成的换,Tarjan算法在深度优先遍历的同时维护一个栈。
当访问到结点 x 时,栈中需要保存以下两类节点:

  • 搜索树上 x 的祖宗节点,记为集合  

    设  。若存在后向边  ,则  与 y 到 x 的路径一起形成环。
  • 已经访问过,并且存在一条路径到达  的节点

    设 z 是一个这样的点,从 z 出发存在一条路径到达  。若存在横叉边  ,则 、z 到 y 的路径、y 到 x 的路径构成一个环。
综上所述,栈中的节点就是能与从 x 出发的“后向边”和“横叉边”形成环的节点。进而可以引入“追溯值”的概念。

追溯值

 设  表示流图的搜索树中以 x 为根的子树。x 的追溯值  定义为满足以下条件的节点的最小时间戳:
   

  • 该点在栈中
  • 存在一条从 出发的有向边,以该点为终点
根据定义,Tarjan算法按照以下步骤计算“追溯值”:
   

  • 当节点 x 第一次被访问时,把 x 入栈,初始化 
  • 扫描从 x 出发的每一条边 

    • 若 y 没有被访问过,则说明  是“树枝边”,递归访问 y ,从 y 回溯后,令
    • 若 y 被访问过且 y 在栈中,则令 

  • 从 x 回溯之前,判断是否有  。若成立,则不断从栈中弹出节点,直至 x 出栈
 下页图中的中括号【】里的数值标注了每个节点的的“追溯值”


强连通分量判定法则 

 在追溯值的计算过程中,若从 x 回溯前,有  成立,则栈中从 x 到 栈顶的所有节点构成一个强连通分量
大致来说,在计算追溯值的第三步,如果  ,那么说明  中的节点不能与栈中其他结点一起构成环。另外,因为横叉边的终点时间必然小于起点时间戳,所以中的结点也不可能直接到达尚未访问的结点(时间戳更大)。综上所述,栈中从 x 到栈顶的所有节点不能与其他结点构成环。
由因为我们及时进行了判定和出栈操作,所以从 x 到栈顶的所有节点独立构成一个强连通分量
 Tarjan算法模板

  1. void tarjan(int u)
  2. {
  3.     dfn[u] = low[u] = timestamp;
  4.     stk[++ top] = u, in_stk[u] = true;
  5.    
  6.     for(int i = h[u]; ~i; i = ne[i])
  7.     {
  8.         int j = e[i];
  9.         if(!dfn[j])
  10.         {
  11.             tarjan(j);
  12.             low[u] = min(low[u], low[j]);
  13.         }
  14.         else if(in_stk[j])
  15.             low[u] = min(low[u], dfn[j])
  16.     }
  17.    
  18.     if(dfn[u] == low[u])
  19.     {
  20.         int y;
  21.         ++ scc_cnt;
  22.         do{
  23.           y = stk[top ++ ];
  24.           in_stk[y] = false;
  25.           id[y] = scc_cnt;
  26.         }while(y != u)
  27.     }
  28. }
复制代码

缩点 

我们可以把每一个 SCC 缩成一个点。对于原图中的每条有向边  若  ,则在编号为  与编号为  的SCC之间连边。
最终,我们会得到一个有向无环图(DAG)
[code]for(int x = 1; x

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

吴旭华

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表