一、前置知识:
   强连通分量:有向图强连通分量:在有向图G中,如果两个顶点vi,vj间(vi>vj)有一条从vi到vj的有向路径,同时还有一条从vj到vi的有向路径,则称两个顶点强连通(strongly connected)。如果有向图G的每两个顶点都强连通,称G是一个强连通图。有向图的极大(看清是极大,不是最大)强连通子图,称为强连通分量(strongly connected components)。一个点x,若没有点与它强连通,则它自己也是一个强连通分量。

二、算法简述

   Tarjan算法是一个主要用于求有向图的强连通分量或无向图的环的算法。(当然也有很多扩展,与其他一些神奇的算法搭配可能会碰撞出奇妙的火花)

   (无向图的极大连通子图就不叫强连通分量了,叫连通分量。实际上求无向图的连通分量用bfs就行了(毕竟无向图没有方向,只要能到达,就是连通,也没有强连通这一说))

三、原理: 

   想象一个情景:从一个点u出发,一直向下遍历,然后忽得找到一个点,那个点x竟然有条指回点u的边!

   那么想必这个点u能够从自身出发再回到自身

   想必这个点u和其他向下遍历的该路径上的所有点构成了一个环,

   想必这个环上的所有点都是强联通的。

   但只是强联通啊,我们需要求的可是强连通分量啊......怎么在退回到这个点的时候,知道所有和这个点u构成强连通分量的点呢?

   开个栈记录就行了。我们建一个栈,保证回溯到u时栈中u及u上面的点组成一个强连通分量,然后把它们弹出、记录就好了。

   似乎做法已经明了了,用程序应该怎么实现呢?

四、程序实现:

    首先需要介绍一些辅助数组

(1)、dfn[ ],表示这个点在dfs时是第几个被搜到的。
(2)、low[ ],表示这个点以及其子孙节点连的这个点及其祖先中dfn最小的值
(3)、stack[ ],表示当前所有可能能构成强连通分量的点。
(4)、vis[ ],表示一个点是否在stack[ ]数组中。

    那么按照之上的思路,我们来考虑这几个数组的用处以及算法的具体过程。

    假设现在开始遍历点u:

    1. 首先初始化dfn[u]=low[u]=第几个被dfs到
      dfn可以理解,但为什么low也要这么做呢?
      因为low的定义如上,也就是说如果没有子孙与u的祖先相连的话,dfn[u]一定是它和它的所有子孙中dfn最小的(因为它的所有子孙一定比他后搜到)。

    2. 将u存入stack[ ]中,并将vis[u]设为true
      stack[ ]有什么用?
      如果u在stack中,u之后的所有点在u被回溯到时u和栈中所有在它之后的点都构成强连通分量。(也就是上文中所说的开个栈记录)

    3. 遍历u的每一个能到的点,如果这个点dfn[ ]为0,即仍未访问过,那么就对点v进行dfs,然后low[u]=min{low[u],low[v]}        
      low[ ]有什么用?        应该能看出来吧,就是记录一个点它最大能连通到哪个祖先节点(当然包括自己)
      如果从u遍历这个点之前这个点就被遍历到了,那么看它当前有没有在stack[ ]里,如果有(要么这个点是u的祖先,要么这个点与u的某个祖先强连通,反正这个点能到达u),说明这个点肯定能到达u,同样u能到达他,他俩强联通,那么low[u]=min{low[u],low[v]}
      如果已经被弹掉了,说明无论如何这个点也不能与u构成强连通分量,因为它不能到达u(当处理强连通分量时才将元素弹出栈。处理包含这个点的强连通分量时没有处理掉u,就说明u不在它的强连通分量里)

    4. 假设我们已经dfs完了u的所有的子树,那么之后无论我们再怎么dfs,u点的low值已经不会再变了。 
      那么如果dfn[u]=low[u]这说明了什么呢?
      再结合一下dfn和low的定义来看看吧
      dfn表示u点被dfs到的时间,low表示u和u所有的子树所能到达的u的祖先中dfn最小的。
      这说明了u点及u点之下的所有子节点没有边是指向u的祖先的了,即我们之前说的u点与它的还在栈中的子孙节点构成了一个最大的强连通图即强连通分量
      此时我们得到了一个强连通分量,把所有的u点以后压入栈中的点和u点一并弹出,将它们的vis[ ]置为false,如有需要也可以给它们染上相同颜色(后面会用到,用于缩点等等)

      代码大概长成这样

      

  

      对了,tarjan一遍不能搜完所有的点,因为存在孤立点或者其他

      所以我们要对一趟跑下来还没有被访问到的点继续跑tarjan

      怎么知道这个点有没有被访问呢?

      看看它的dfn是否为0!

      

    

      非常简短的tarjan复杂度证明:

        思考每个点最多被dfs一次,所以均摊下来复杂度是O(n)的

        证毕

五、扩展:

  tarjan缩点:

    1.什么时候要用缩点

    众所周知,有向无环图总是有着一些蜜汁优越性,因为没有环,你可以放心的在上面跑dfs,搞DP,但如果是一张有向有环图,事情就会变得尴尬起来了

    思考一下会发现如果不打vis标记就会t飞(一直在环里绕啊绕),但是如果打了,又不一定能保证最优解

    而你一看题目却发现显然根据一些贪心的原则,这个环上每个点的最大贡献都是整个环的总贡献

    这个时候缩点就显得很有必要了,因为单个点的贡献和整个环相同,为什么不去把整个环缩成一个超级点呢?

    这个环只是为了好理解,事实上他应该是一个强连通分量,显然如果只缩掉一个强连通图,图中仍然有环存在

    缩点的一个栗子

     -----------> 

    2.怎么缩点

    还记得之前tarjan里的染色吗?

    我们只需要把同一颜色的点权加到一块,然后把该颜色指向不同颜色的边建好就可以了

    代码就不贴了,因为不同的题有不同的处理方法

  无向图tarjan求环:

    每次tarjan递归时记录父亲节点到儿子节点走的边的对应相反边(因为无向图对于一条边用前向星存的话要插入2次),若儿子在不走这条边的情况下仍能得到小于dfn的low,即可走另一条路径到达父亲节点的祖先,说明有一个环。并且若干个相交的环会以一个强连通分量的形式呈现出来。

  无向图tarjan求割点:www.cnblogs.com/collectionne/p/6847240.html

      该博客有一处low的维护操作与普通tarjan不同:low[u] = min(low[u], dfn[v]);

      这里解释一下:若v已经被遍历过了,这时遍历到u发现u与v有连边。这说明什么?v还没有被回溯。因为这时无向图,既然u能到v,那v也能到u,当回溯到v时,v所能到的点必然都已经被遍历完了。这里刚遍历到u,不就说明v还没有被回溯到嘛。这样,v不就是u的祖先了嘛。

大量摘自洛谷博客:初探tarjan算法(求强连通分量),略有修改。在此对作者:Styx  表示真挚的感谢。

  

Tarjan算法初步的更多相关文章

  1. 【原创】tarjan算法初步(强连通子图缩点)

    [原创]tarjan算法初步(强连通子图缩点) tarjan算法的思路不是一般的绕!!(不过既然是求强连通子图这样的回路也就可以稍微原谅了..) 但是研究tarjan之前总得知道强连通分量是什么吧.. ...

  2. 强连通分量与tarjan算法初步运用

    模板题:B3609 [图论与代数结构 701] 强连通分量 题目描述 给定一张 n 个点 m 条边的有向图,求出其所有的强连通分量. 注意,本题可能存在重边和自环. 输入格式 第一行两个正整数 n , ...

  3. 图论初步-Tarjan算法及其应用

    暑假刷了一堆Tarjan题到头来还是忘得差不多. 这篇博客权当复习吧. 一些定义 无向图 割顶与桥 (划重点) 图G是连通图,删除一个点表示删除此点以及所有与其相连的边. 若删除某点u后G不再连通,那 ...

  4. 有向图强连通分量的Tarjan算法

    有向图强连通分量的Tarjan算法 [有向图强连通分量] 在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强连通(strongly connected).如果有向图G的每两个顶点都强连通,称G ...

  5. 点/边 双连通分量---Tarjan算法

    运用Tarjan算法,求解图的点/边双连通分量. 1.点双连通分量[块] 割点可以存在多个块中,每个块包含当前节点u,分量以边的形式输出比较有意义. typedef struct{ //栈结点结构 保 ...

  6. 割点和桥---Tarjan算法

    使用Tarjan算法求解图的割点和桥. 1.割点 主要的算法结构就是DFS,一个点是割点,当且仅当以下两种情况:         (1)该节点是根节点,且有两棵以上的子树;         (2)该节 ...

  7. Tarjan算法---强联通分量

    1.基础知识 在有向图G,如果两个顶点间至少存在一条路径,称两个顶点强连通(strongly connected).如果有向图G的每两个顶点都强连通,称G是一个强连通图.非强连通图有向图的极大强连通子 ...

  8. (转载)LCA问题的Tarjan算法

    转载自:Click Here LCA问题(Lowest Common Ancestors,最近公共祖先问题),是指给定一棵有根树T,给出若干个查询LCA(u, v)(通常查询数量较大),每次求树T中两 ...

  9. 强连通分量的Tarjan算法

    资料参考 Tarjan算法寻找有向图的强连通分量 基于强联通的tarjan算法详解 有向图强连通分量的Tarjan算法 处理SCC(强连通分量问题)的Tarjan算法 强连通分量的三种算法分析 Tar ...

随机推荐

  1. MySQL -2- 体系结构--随笔小记

    简介与安装NoSQLRDBMS版本安装方式二进制安装,源码安装体系结构CS模型TCP/IPsocketmysql master thread 实例mysqld 的程序构成连接层 协议.验证.链接线程S ...

  2. 面试官:new一个对象有哪两个过程?

    Java在new一个对象的时候,会先查看对象所属的类有没有被加载到内存,如果没有的话,就会先通过类的全限定名来加载.加载并初始化类完成后,再进行对象的创建工作. 我们先假设是第一次使用该类,这样的话n ...

  3. springboot笔记-文件上传

    使用 Spring Boot 和 Thymeleaf 上传文件 Spring Boot 利用 MultipartFile 的特性来接收和处理上传的文件,本示例前端页面使用 Thymeleaf 来处理. ...

  4. BZOJ 4857 反质数序列

    题面 奇数+奇数一定不是质数(1+1除外),偶数+偶数一定不是质数,质数只可能出现在偶数+奇数中 把所有的点排成两列,权值为奇数的点在左边,权值为偶数的在右边 如果左边的点x+右边的点y是质数,我们就 ...

  5. ubuntu 设置sudo 免密码

    一. 修改sudoers的权限 二. 修改sudoers 文件 <1>. 在文件最后一行添加yourusername ALL=(ALL) NOPASSWD : ALL 三. 修改回sudo ...

  6. 断言(assert)

    断言是编程术语,表示为一些布尔表达式,程序员相信在程序中的某个特定点该表达式值为真,可以在任何时候启用和禁用断言验证,因此可以在测试时启用断言而在部署时禁用断言. 简单点说,断言指的就是,将结果判断说 ...

  7. wex5 sqllite本地数据库的运用

    http://doc.wex5.com/?p=3774 需要引入包require("cordova!com.brodysoft.sqlitePlugin"); //本地数据库操作 ...

  8. 移除数组中指定键(Yii2)

    /** * 移除数组中指定key * @param $data * @param $key * @return array */ public static function removeKey($d ...

  9. kali 下安装 vmtools

    网上的教程都是默认路径下的,kali是定制版本的,路径不同,所以首先要找到media下安装包的路径,然后进入该路下,将安装包复制到想要的路径下,并解压缩到想要的路径下,剩下的就跟网上的差不多了,即找到 ...

  10. VIM如何自动保存文件、自动重加载文件、自动刷新显示文件

    1.手动重加载文件的命令是:e! 2.一劳永逸的方法是:vim提供了自动加载的选项 autoread,默认关闭. 在vimrc中添加 set autoread即可打开自动加载选项,相关选项: :hel ...