“tarjan陪伴强联通份量 生成树完成后思路才闪光 欧拉跑过的七桥古塘 让你 心驰神往”----《膜你抄》
引用来自度娘的一句话:html
“有向图强连通份量:在有向图G中,若是两个顶点vi,vj间(vi>vj)有一条从vi到vj的有向路径,同时还有一条从vj到vi的有向路径,则称两个顶点强连通(strongly connected)。
若是有向图G的每两个顶点都强连通,称G是一个强连通图。有向图的极大强连通子图,称为强连通份量(strongly connected components)。”数组
一脸懵逼......不过倒也不难理解。spa
反正就是在图中找到一个最大的图,使这个图中每一个两点都可以互相到达。这个最大的图称为强连通份量,同时一个点也属于强连通份量。code
如图中强连通份量有三个:1-2-3,4,5component
噫......固然,经过肉眼能够很直观地看出1-2-3是一组强连通份量,但很遗憾,机器并无眼睛,因此该怎么判断强连通份量呢?htm
若是还是上面那张图,咱们对它进行dfs遍历。blog
能够注意到红边很是特别,由于若是按照遍历时间来分类的话,其余边都指向在本身以后被遍历到的点,而红边指向的则是比本身先被遍历到的点。get
若是存在这么一条边,那么咱们能够yy一下,emmmm.......class
从一个点出发,一直向下遍历,而后忽得找到一个点,那个点居然有条指回这一个点的边!遍历
那么想必这个点可以从自身出发再回到自身
想必这个点和其余向下遍历的该路径上的全部点构成了一个环,
想必这个环上的全部点都是强联通的。
但只是强联通啊,咱们须要求的但是强连通份量啊......
那怎么办呢?
咱们仍是yy出那棵dfs树
不妨想一下,何时一个点和他的全部子孙节点中的一部分构成强连通份量?
他的子孙再也没有指向他的祖先的边,却有指向他本身的边
由于只要他的子孙节点有指向祖先的边,显然能够构成一个更大的强联通图。
好比说图中红色为强连通份量,而蓝色只是强联通图
那么咱们只须要知道这个点u下面的全部子节点有没有连着这个点的祖先就好了。
但彷佛还有一个问题啊......
咱们怎么知道这个点u它下面的全部子节点必定是都与他强联通的呢?
这彷佛是不对的,这个点u之下的全部点不必定都强联通
那么怎么在退回到这个点的时候,知道全部和这个点u构成强连通份量的点呢?
开个栈记录就好了
什么?!这么简单?
没错~就是这么简单~
若是在这个点以后被遍历到的点已经能与其下面的一部分点(也可能就只有他一个点)已经构成强连通份量,即它已是最大的。
那么把它们一块儿从栈里弹出来就好了。
因此最后处理到点u时若是u的子孙没有指向其祖先的边,那么它以后的点确定都已经处理好了,一个常见的思想,能够理解一下。
因此就能够保证栈里留下来u后的点都是能与它构成强连通份量的。
彷佛作法已经明了了,用程序应该怎么实现呢?
因此为了实现上面的操做,咱们须要一些辅助数组
(1)、dfn[ ],表示这个点在dfs时是第几个被搜到的。
(2)、low[ ],表示这个点以及其子孙节点连的全部点中dfn最小的值
(3)、stack[ ],表示当前全部可能能构成是强连通份量的点。
(4)、vis[ ],表示一个点是否在stack[ ]数组中。
那么按照之上的思路,咱们来考虑这几个数组的用处以及tarjan的过程。
(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[ ]有什么用?
应该能看出来吧,就是记录一个点它最大能连通到哪一个祖先节点(固然包括本身)
若是遍历到的这个点已经被遍历到了,那么看它当前有没有在stack[ ]里,若是有那么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全部的子树所能到达的点中dfn最小的。
这说明了u点及u点之下的全部子节点没有边是指向u的祖先的了,即咱们以前说的u点与它的子孙节点构成了一个最大的强连通图即强连通份量
此时咱们获得了一个强连通份量,把全部的u点之后压入栈中的点和u点一并弹出,将它们的vis置为false,若有须要也能够给它们打上相同标记(同一个数字)
对了,tarjan一遍不能搜完全部的点,由于存在孤立点或者其余
因此咱们要对一趟跑下来尚未被访问到的点继续跑tarjan
怎么知道这个点有没有被访问呢?
看看它的dfn是否为0!
这看起来彷佛是o(\(n^2\))的复杂度,但其实均摊下来每一个点只会被遍历一遍
因此tarjan的复杂度为o(\(n\))。