一、前置知识:
   强连通分量:有向图强连通分量:在有向图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. 记一次神奇的codeforces

    今天有一场codeforces的div3,时间挺合适,于是就想打.结果发现rating超过1600就不能报名.虽然shzr好久不打CF了而且很菜,但是毕竟还是到了1600的,于是和ZUTTER_一起用 ...

  2. Java编程思想读书笔记 第十章 内部类

    非静态内部类作用: 最基本的作用:名字隐藏和组织代码 用例:内部类不访问外部类的元素时可以直接new.(bad style!) 用例:通过外部类的非静态方法返回内部类的引用,这样隐含了内部类对象和其对 ...

  3. 06、CEL文件与灰度图像

    R语言里的image方法可以绘制CEL文件的灰度图像.我们先来讨论image这个的函数: 如:x <- c(0:2) y <- c(0:2) m <- matrix(c(1,5,10 ...

  4. Kibana server is not ready yet出现的原因

    第一点:KB.ES版本不一致(网上大部分都是这么说的) 解决方法:把KB和ES版本调整为统一版本 第二点:kibana.yml中配置有问题(通过查看日志,发现了Error: No Living con ...

  5. node(koa2)跨域与获取cookie

    欲做一个node 的网关服务,通过 cookie 做信息传递,选择框架 koa2,这里简单记录跨域处理以及 cookie 获取. 首先:解决跨域问题,使用 koa2-cros 来处理,跨域问题后端处理 ...

  6. solve update pip 10.0.0

    The bug is found in pip 10.0.0. In linux you need to modify file: /usr/bin/pip from: from pip import ...

  7. jmeter的三种参数化方法

    JMeter的三种参数化方式包括: 1.用户参数 2.函数助手 3.CSV Data Set Config 一.用户参数 位置:添加-前置处理器-用户参数 操作:可添加多个变量或者参数 二.函数助手 ...

  8. python实现加密的方式总结

    python实现加密的方式总结 原文地址 目录 基础知识扫盲 Base64 MD5 DES 3DES AES RSA 基础知识扫盲 对称加密 对称密钥加密 , 又叫私钥加密.即信息发送的方和接受方用一 ...

  9. 国产芯片选型手册及厂商名录 版本V2019

  10. 升级完pip后出错:Traceback (most recent call last): File "/usr/bin/pip", line 11, in <module> sys.exit(__main__.main())

    今天在ubuntu上升级了pip,之后执行pip命令的时候就报错了: Traceback (most recent call last):   File "/usr/bin/pip" ...