第一部分------求强连通分量
1.强连通分量的概念
"有向图强连通分量:在有向图G中,如果两个顶点vi,vj间(vi>vj)有一条从vi到vj的有向路径,同时还有一条从vj到vi的有向路径,则称两个顶点强连通(strongly connected)。如果有向图G的每两个顶点都强连通,称G是一个强连通图。有向图的极大强连通子图,称为强连通分量(strongly connected components)。" -------------来源:百度百科
我的理解就是在有向图中找到一个最大的子图,使得这个子图的任意两点都能够相互到达,那么这个子图就被称作强连通分量,如果只有一个点那么也称之为强连通分量,但是如果这个子图增加一个点后任然符合"任意两点都能够相互到达"那就不能称之为强连通分量,而是强连通图
图中ABC可相互到达,所以(A,B,C)为一个强连通分量,D为一个强连通分量,共两个强连通分量
图中1234四个点可相互到达,所以(1,2,3,4)可相互到达,5和6各为一个强连通分量,共三个强连通分量
2.如何求强连通分量
这里我介绍的方法是Tarjan算法
依旧先举一个图例
我们现在从1开始对它进行一个dfs遍历,按照遍历的深度,1为第一层,2和3为第二层,4和5为第三层,6为第四层(可以自己在纸上画一画),我们发现4->1这条红线代表的有向边十分特别,因为其他的有向边都朝着更深的层次的结点,而这条特殊的边却指向了比自己深度更小的结点.
现在我们假设存在这么一条边,从一个点i出发,一直向下遍历,然后到达了一个点j时,我们发现这个j点竟然有一条指向i的边!那么显然从点j出发可以回到点i从而再次回到点j,显然从i到j这条路径上的所有点与i和j共同构成了一个环,而这个环上的所有点必然是强连通的, 但是我们要求的是强连通分量,而不只是强连通图.
那么我们先来思考一个问题:什么时候一个点u和它的子孙节点中的一部分构成强连通分量呢? 答案是:只要u的子孙节点再也没有指向u的祖先结点的有向边,而有指向u的有向边.我来解释一下这是为什么:因为只要u的子孙结点有指向u的祖先结点的有向边,那么就能构成一个更大的强连通图.为了方便理解,所以我再次举出一个图例.
图中(1,2,3,4)为强连通分量,而(2,3,4)中因为2的子结点指向了2的父节点1从而不符合强连通分量的概念,仅仅是一个强连通图.
但是u的子孙节点不一定都强连通呀,那么我们怎样才能在退回到u的时候知道有哪些点和u构成强连通分量呢?答案是维护一个栈.那么如何使用这个栈呢?如果在u之后被遍历到的点已经能与其下面的一部分点(也可能就只有他一个点)已经构成强连通分量,那么把它们一起从栈里弹出来就行了。所以最后回溯到点u的时候,如果u的子孙结点没有指向u的祖先结点的边,那么u之后的点肯定已经判断好是否联通了,所以就可以保证在栈中u和u之后的点是否能与u构成强连通分量.
可能这个过程比较抽象,所以我给出了一个例子方便理解
思路大概梳理完了,那么我们应该想一想怎样实现这个算法了:
为了实现tarjan算法,我们需要维护三个数组
1) Dfn[i], 记录i是第几个被搜索到的
2) Low[i], 记录i及其子孙节点的有向边所连的点中dfn[]最小的值
3) InStack[i], 表示i是否在栈内
tarjan开始√
step1: 初始化Dfn[i] = low[i] = ++deep;
step2: 将当前结点cur入栈, InStack[cur] = 1;
step3: 遍历u的所有出边,设抵达的下一个结点为v,如果v未曾被访问过(即Dfn[v] = 0),那么对v进行dfs, 之后更新Low[cur]的值,即Low[cur] = min{Lo[cur], Low[v]}.
step4: 如果Dfn[cur] = Low[cur], 也就是说cur的子结点没有再指向cur的祖先结点, 那么它就构成了一个强连通分量, 将栈中cur和栈内cur之后的结点出栈, 这些结点就构成了一个强连通分量, 并给这些结点标记它们是第几个强连通分量中的结点, 最后将他们的InStack置0.
tarjan结束√
step4中我们为什么这么做呢,因为dfs肯定将cur后的结点都处理完了,也就是cur的子树中的强连通分量已经求完了, 自然u与剩下的结点就构成了强连通分量.
那么我们用一题模版题来结尾
洛谷OJ: P2921 [USACO08DEC]在农场万圣节Trick or Treat on the Farm(强连通分量)
第二部分------缩点
第三部分------求割点、桥