一、tarjan求有向图强连通分量
这是tarjan算法应用最简单的一个方面了。
对于什么都不会的萌新,先介绍一下各个概念。
强连通:在一个有向图中,对于两个点i, j,如果存在路从i通向j,存在路从j通向i,那么称这两个点强连通。
如果一个图中任意两个点强连通,那么称这个图为强连通图。
一个图的极大子图满足强连通,那么称这个子图为强连通分量。
那么问题来了,我们怎么理解强连通分量呢?
其实,你可以找出一个环,这个环里的所有的点就组成了一个强连通分量。
比如这个图,在这个环2->3->4->2中,任意两个点能互相到达,那么2、3 、4就组成了这个图的一个强连通分量
注意:一个点也是强连通分量
所以,这个图的所有强连通分量就是:
set1: 1
set2: 2 3 4
set3: 5
共有三个强连通分量。
好的,回到正题,tarjan算法其实就是一个DFS的过程,现在,我们开一个数组,记录下我们遍历这个图的顺序。一般这个数组都叫DFN。
如果从1开始DFS,那么,我们把遍历的顺序写成一个DFS树是这样的(如果从其他点开始,DFN数组会不一样,但是结果是一样的),如图:
现在DFN数组里的状态为:
DFN[1] = 1, DFN[2] = 2, DFN[3] = 3, DFN[4] = 4, DFN[5] = 5.
代表了遍历的顺序。
很明显,图中是有一条回路的,这条回路在哪呢?
我们可以看到,在DFS树中,只要一个节点指向了他的一个父节点,那么肯定形成了回路,从起点到终点内包含的点组成了一个强连通分量。
现在的问题是,怎么找到有没有这样往回指的边,而且要找到这个回路的包含的点。
现在我们再来一个数组,一般叫low, low[i]表示i号节点能向上指的的节点中有最小DFN的那个节点 的DFN值,在上面那个图中,low[4] = 2。
那么三号节点呢?因为3号可以通过3->4->2指向2号节点,所以low[3] = 2。
注意:low的值不是节点的编号,而是那个节点的DFN值,在上面的那个情况中只是恰好i号节点的DFN值就是i。
也就是说,所有拥有相同low值的节点,在同一个强连通分量中,我们只要找出有相同low值的点组成的集合就可以了。
怎么找呢?
我们建一个栈,按照DFS的顺序把数push进栈里,只要在求完DFN和LOW数组后,在递归的过程中,碰见一个点i, 它的LOW[i] == DFN[i],那么就从栈里pop出数,直到pop出i,这些数就是low为DFN[i]的一个集合。
具体见图:
先看左图,遍历到4号的时候,发现碰见了2,能指回去,那么在递归的过程中就把low[3],和low[4] 都赋值为了2(DFN[2] = 2),然后后退到了2号节点,此时发现low[2] == dfn[2],此时开始pop,low[2],low[3], low[4]都是2,把4,3,2都pop出来,那么2,3,4就是一个强连通分量
用栈pop的过程,其实就是一个寻找相同low值的过程,这个过程比较难理解些,最好出个数据模拟,自己多思考些.
求割点和桥
还是先给概念:
割点:去掉某个点后,一个连通图被分解成两个不能互相到达的图,那么这个点就称为割点
桥(割边):去掉某条边后,一个连通图被分解成两个不能互相到达的图,那么这条边就成为桥
PS:割点和桥是无向图里的概念,所以下面提到的图是无向图.
假如这是一颗DFS树(即上面的DFN数组,虽然是无向图,但也有它的DFN数组)
桥:
如果一条边(u->v)是桥,那么下面的那个点能往上翻的最高的点,不能到达或超过上面的那个点
以上图(2->3)这条边为例,DFN[2] = 2, LOW[3] = 3,点3再怎么往上翻,也不能到达2或者2以上,那么,2->3就是一个桥
再看(3->4),DFN[3] = 3, Low[4] = 3,此时,下边的点4能往上翻到点3,也就是到达了点3,此时,(3,4)两个点有两条通路,(3->4), (3->5->4),也就不是桥
也就是:对于相邻的两个点u,v,若DFN[u] < Low[v],则有边(u->v)是桥.
割点:
如果你明白了上面的过程,那么割点就不难理解了.
如果一个点是割点,有两种情况:
1. 这个点是DFS树的根节点,那么只要这个点有两个儿子,这个点就是一个割点.
2. 这个点不是根节点,那么只要满足其所有儿子节点网上翻不能超过这个点(可以是到达)参考上图,3是割点,2是割点,4不是割点.
总结
其实tarjan算法最主要的就是一个DFN数组和一个LOW数组,对应着图上每个点的遍历顺序和每个点在DFS树中最往上能翻到哪个点.求出这两个数组,就可以用这两个数组求强连通分量,割点和桥.