Tarjan的极大强连通子图(strongly connected components,SCC)算法基于深度优先遍历(DFS)实现。本文就尝试从深度优先遍历的角度思考一下Tarjan的方法是如何找出SCC的。

深度优先遍历

深度优先遍历可以说是求SCC比较直观的一个途径。例如对于下图来说,我们从A开始遍历,如果能够回到A,那么整个路径就是一个连通分量。例如下图中的A -> B -> D就是一个连通子图,但不是极大连通子图。我们可以看到单纯的深度优先遍历并不能找出SCC,我们需要将已经找到的连通分量记录下来,例如下图中的路径A -> B -> D,然后和后面遍历得到的点结合起来组成极大连通子图。


注:该图来源于http://blog.miskcoo.com/2016/07/tarjan-algorithm-strongly-connected-components

深度优先遍历 with 集合

在前一小节中,我们发现普通的深度优先遍历并不能得出极大连通子图,因此需要一个数据结构记录下来深度优先遍历时的路径,由于连通子图中各个节点是平等的,只是遍历顺序不同,所以我们可以尝试使用集合保存遍历时的路径。同时我们可以观察到,连通分量可以 “塌缩“ 为一个节点ABD,任何与ABD构成连通的节点,也与ABD中所有的子节点连通。如下图所示,我们将ABD塌缩成为节点ABD


注:为了表示一致,我们也将C塌缩成一个节点

假如在我们遍历完ABCD之后,按照先遍历E的顺序继续进行图的遍历,那么我们可以观察到,EF能与ABD进行连通,所以我们可以将EFABD一起塌缩成节点ABDEF,如下图所示。

在遍历完EF之后,我们继续遍历GH之后,发现GH也可以与ABDEF连通,那么我们同样可以将GHABDEF一起塌缩成为ABDEFGH,最终得到下图。

如上图所示,我们得到强连通分量ABDEFGHCI。由于很多细节没有说明,所以整个过程还是很简单的。

细节:

  • 如何表示一个塌缩节点?一种有效的方式就是给该塌缩节点中的所有子节点,都分配一个相同的scc-index,例如都为其分配节点1。由于是深度优先遍历,我们可以在返回父节点的时候,依次设置其scc-index
  • 在遍历的过程中,如何判断一个强连通分量已经形成?我们可以在遍历完之后,遍历所有的节点,scc-index相同的节点就构成了一个强连通分量

由上我们可以得出如下的一种可行方案:

  • 在遍历的过程中,我们可以为每个节点分配一个遍历序号,第一个遍历的为1,第二个遍历的为2等等
  • 如果在遍历的过程中,遇到了在同一条遍历链条上的节点(也就是祖先节点),那么说明我们得到了一个连通分量,此时我们该链条上的所有节点的scc-index置为该祖先节点的遍历序号。然后将该链条(环)“塌缩“成一个节点
  • 在遍历的过程中,如果遇到了两个“塌缩“节点需要合并的情况,那么就选择scc-index低的节点作为新的“塌缩“节点的scc-index
  • 没有形成“塌缩“节点的节点,独自成为一个新的节点

有了如上的一个方案后,我们重新执行一遍深度优先遍历之后,过程如下图所示:

注:此次为了说明两个“塌缩“节点如何合并,将遍历顺序改成了先遍历GH,再遍历F

Tarjan’s算法

Tarjan’s算法的核心如下:

  • Tarjan’s算法选择任一点进行深度优先遍历,每个节点只遍历一次
  • Tarjan’s算法将遍历过的节点,按照遍历顺序依次存放到栈上。其中:栈维护一个不变量,任意一个节点保留在栈上,当且仅当该节点遍历完之后,存在一条指向栈上更早节点的路径
  • 对于每个节点v,Tarjan’s算法会按照遍历顺序为为其分配一个唯一的遍历序号v.index,还会记录该节点能够访问到的遍历序号最小的节点序号(包括v自身),记为v.lowlink。结合上面的第二点,我们能够推断得出,节点v保留在栈上的条件是v.lowlink < v.index。其中将v.lowlink == v.index的节点,称为强连通图的root节点。另外,v.lowlink的初始值为v.index

注:上面的内容精简自https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm

介绍如何更新v.lowlink之前,需要了解tree edgeback edgecross edge,这三种边是深度优先遍历中生成树中边的概念。v.lowlink的更新有如下两种情况:

  • Tree Edge
    If node v is not visited already, then after DFS of v is complete, then minimum of u.lowlink and v.lowlink will be updated to u.lowlink.
    u.lowlink = min(u.lowlink, v.lowlink)
  • Back Edge
    When child v is already visited, then minimum of low[u] and Disc[v] will be updated to low[u].
    u.lowlink = min(u.lowlink, v.index)

我们以最开始的图为例,给出Tarjan’s算法的整个过程:

整个过程其实和前面介绍的“深度优先遍历with集合“的过程很相似,例如对于上图中的第(4)步,是在遇到了back-edge以后,应用规则2,将D.lowlink设置为A的遍历序号1,然后应用规则1,在回退的过程中将BAlowlink设置为1。其实这里的ABD组成了一个连通分量,可以理解成将这三点塌缩成ABD,该连通分量的标号为1(也就是连通分量“根“的遍历序号)。

上图中第(6)步,是在遇到了back-edge以后,应用规则2,将F.lowlink设置为D的遍历序号4,然后应用规则1,在回退的过程中将FElowlink设置为4,这里的DFE也是一个连通分量,同样可以理解成一个塌缩节点EFD,该塌缩节点的序号为4。同理的还有第(10)步中的连通分量EGH,序号为5。这三个连通分量如下图所示:

从上图中我们可以看出,lowlink可以看作是一系列连通分量的标号,标号是连通分量最早遍历的节点的遍历序号,也就是root节点的遍历序号。上图中有三个连通分量,145,这三个连通分量也是互相连通的,所以最终这三个连通分量最终组成了极大连通子图。注意连通分量4的根节点Dindex为4,lowlink为1,说明该节点从属于另外一个连通分量1。因此Tarjan’s算法从另一个角度来看,是首先得到了一系列的连通分量,最后通过这些连通分量来构成更大的连通子图。

Why u.lowlink = min(u.lowlink, v.index)

通过这个角度,我们可以解决如下的疑惑,就是规则2中,为什么u.lowlink = min(u.lowlink, v.index),而不是u.lowlink = min(u.lowlink, v.lowlink)其实这里用v.index还是v.lowlink对于结果没有任何区别

  • Back Edge
    When child v is already visited, then minimum of low[u] and Disc[v] will be updated to low[u].
    u.lowlink = min(u.lowlink, v.index)

首先lowlink看作是连通分量的序号,如果遇到了回边,说明遇到了一个新的连通分量,我们要为这个连通分量分配一个序号,如果使用v.lowlink,例如在(5)步中,我们将F.lowlink设置为D.lowlink,那么就说明我们将F合并进了原有连通分量1中。

也就是说如果使用u.lowlink = min(u.lowlink, v.lowlink),那么节点都是以渐增的方式扩展连通分量的。
如果使用u.lowlink = min(u.lowlink, v.index),那么节点是先构成一系列的子连通分量,然后再将这些子连通分量构成更大的连通分量。

下面我们采用u.lowlink = min(u.lowlink, v.lowlink),再走一遍Tarjan’s算法的过程,如下所示。

注:这是自己一个简单的想法,写了个初步的文档,并没有严格证明。

Tarjan's strongly connected components algorithm的一些想法相关推荐

  1. 强连通分量(strongly connected components)

    强连通分量(strongly connected components)    徐不可说        2018/8/4                                        ...

  2. FDS-HW11 6-1 Strongly Connected Components

    Strongly Connected Components 堆栈C语言实现:https://www.cnblogs.com/tingshuo123/p/7090858.html 思路分析 使用tarj ...

  3. JavaScript实现strongly Connected Components 强连通分量算法(附完整源码)

    JavaScript实现strongly Connected Components 强连通分量算法(附完整源码) Comparator.js完整源代码 LinkedListNode.js完整源代码 L ...

  4. connected components algorithm连通组件算法

    connected components algorithm连通图算法 什么是connected components algorithm? 用通俗的话说就是一个图像的前景部分有几部分构成,用下面的这 ...

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

    强连通分量(SCC, Strongly Connected Component) 强连通分量的概念 强连通分量的应用 强连通分量的算法--Tarjan算法 强连通分量的概念 在有向图中,任意两个顶点 ...

  6. 图论学习六之Strongly connected components强连通分量

    强连通分量(Strongly connected cmponents) • 在有向图G中,如果任意两个不同的顶点相互可达,则称该有向   图是强连通的.有向图G的极大强连通子图称为<

  7. C#,图论与图算法,寻找图强连通单元(Strongly Connected Components)的罗伯特·塔扬(Robert Tarjan‘s Algorithm)算法与源程序

    Tarjan算法是一种高效的图算法,它利用图的深度优先搜索遍历,在线性时间内找到有向图中的强连通分量.使用的关键思想是,强连通组件的节点在图的DFS生成树中形成子树. 将有向图划分为强连通分量的任务非 ...

  8. 6-10 Strongly Connected Components(30 分)

    为了便于测试也写了ReadG() 自己测试没问题,但目前仍无法通过测试样例 怀疑是结构体指针的分配与题目用意不符, 另外孤立点的输入格式不明 Tarjan算法参考修改自: http://blog.cs ...

  9. [PTA] Strongly Connected Components

    算法参考了网上资料(Tarjan算法),嫌麻烦并没有使用给出的函数指针. #include <stdio.h> #include <stdlib.h>#define MaxVe ...

最新文章

  1. HTML5与CSS3基础(二)
  2. python报错 SyntaxError: invalid character in identifier
  3. 加快Android Studio的编译速度
  4. 【经典回放】多种语言系列数据结构算法:串(C版)
  5. WAP2.0(XHTML MP)基础介绍
  6. HttpURLConnection中connection.getInputStream()报异常FileNotFoundException
  7. stm32-再谈GPIO
  8. Flutter进阶—网络和HTTP
  9. 微信小程序转发,发送朋友圈(uni-app)
  10. 不瞒你说,我最近跟Java源码杠上了
  11. Hadoop概述--四大组件架构及其关系
  12. 在微型计算机汉字系统中一个汉字机内码,一个汉字的机内码在计算机中用2个字节表示。...
  13. 论文阅读:Bridging Knowledge Graph to Generate Scene Graph(ECCV20)
  14. 面试时要怎么做自我介绍呢?
  15. 迅雷android 电视,【迅雷电视助手】迅雷电视助手安卓版(Android)2.0下载_太平洋下载中心...
  16. 淘宝美工掌握这4种能力,不逊色于UI设计师
  17. 谈谈面试题之为什么用线程池?解释下线程池参数?
  18. 丝滑!CVPR 2021 视频插帧论文+开源代码汇总
  19. 利用requests库和Xpath爬取猫眼电影榜单【Python】
  20. 动态添加/注册路由之addRoutes

热门文章

  1. git 给gitlab 添加公钥
  2. XFF与Referer(含实操)
  3. 谷歌MapReduce论文学习
  4. Xcode更新,提示空间不足,踩过的一个坑!
  5. Linux和UNIX“新手”们必备的与Linux系统编程相关的基本概念
  6. 图形图像处理 - 手写 QQ 说说图片处理效果
  7. 山东大学软件工程应用与实践——使用CUDA/GPU技术加速密码运算(第七周)
  8. 查询网页服务器的IP地址
  9. 2021HaaS新春大拜年
  10. RationalDMIS 2020运行程序时自动输出到Excel摸板