求强连通分量的双DFS,Tarjan和Gobow算法详解

1.强连通分量-----双DFS算法思想:对一个有向图作两遍DFS,第一遍DFS能确定图中每个顶点的DFS完成时间,第二遍DFS从第一遍DFS完成时间的逆序开始遍历。



思想:对一个有向图作两遍DFS,第一遍DFS能确定图中每个顶点的DFS完成时间,第二遍DFS从第一遍DFS完成时间的逆序开始遍历,这时得到的一棵棵深度优先搜索树就是一个个对应的强连通分量。


举例:

求强连通分量的双DFS,Tarjan和Gobow算法详解_第1张图片



对于上面这个有向图,我们以C为源节点进行第一遍DFS,可以得到每一个顶点的(访问时间/完成时间).也就是我们能得到顶点DFS完成时间,逆序为:b->e->a->c->d->g->h->f。



然后在第一遍DFS的基础上进行第二遍DFS,从逆序开始,即以b为源节点开始深度优先搜索,但这个时候我们要把图反向,得到下边这样一个图,为什么要反向呢?我们在下边讲解。下边就是一个反向图。

求强连通分量的双DFS,Tarjan和Gobow算法详解_第2张图片
逆序即以b为源节点开始,会得到b->a->e; c->d;  g->f;  h;四棵子树,即四个强连通分量。


现在解释一下为什么要先反向?


以C结点为例,图未反向时,我们从C能遍历到的结点有,c->g->h->f->d。


 图反向后,我们从C能遍历到的结点有,c->d。(注意图反向后,我们是从b开始DFS的)从这里我们可以看出c,d就是一个强连通子图。因为从c的正向出发可以到达d,从反向出发也能到达d,那就说明c,,d之间两两都有路径。


为什么第二遍DFS要从逆序开始呢?


我们发现从逆序开始,得到的子树恰好就是一个强连通分量。因为我们从逆序开始不可能访问到本强连通分量以外的顶点。为什么呢?假设在反向后的图中存在一条从框bae到框cd的边,那么在原图中肯定存在一条从框cd到框bae的边,即在第一遍DFS时,从c出发可以访问到b,a,e。那么b,a,e的完成时间肯定比c要早(孩子结点的完成时间比祖先结点要早),与b完成时间最晚矛盾,所以不可能存在一条从框bae到其他框的边,即从b出发只能访问到本强连通分量的顶点。如果我们不从逆序出发,假设从c出发,那么就不能保证只访问到本强连通分量顶点这一性质了。


2.强连通分量---Tarjan算法


以下图作说明。

求强连通分量的双DFS,Tarjan和Gobow算法详解_第3张图片
Tarjan算法中用到一次DFS,主要用到下边这两个数组。


DFN[nei(i)]表示当前未访问出边的邻接点,LOW[son(i)]表示深度优先树中i的孩子结点,当i没有孩子结点时,取它本身。例如,我们从1号顶点出发,访问到3,访问到5,访问到6,(自己画个栈辅助看看)这时我们发现6号顶点没有未访问的邻接点了,而且无未访问出边,它也没有孩子结点,所以等于它本身,满足DFN=LOW(这是出栈条件),我们出栈,即6号顶点是一个强连通分量。回溯到5,发现5号顶点无未访问的出边,选min(LOW[i],LOW[son(i)])而son(i)就是6号顶点,low值为4,选小的,还是3,即5号顶点满足DFN=LOW,出栈,5号也是一个强连通分量。回溯到3,继续访问4,进栈,这时候4没有未访问的邻接点了,但它有未访问的出边,(4->1).根据公式,此时DFN[nei(i)]就是1号顶点,它的DFN值为1,所以选小的,4号顶点的low值变为1,不满足出栈条件,继续回溯到3,因为4号顶点是3的孩子结点,而且3号顶点无未访问的出边,根据公式能知道3号的low值也为1,不满足出栈条件,继续回溯到1,访问2号顶点,此时2号有一条未访问的出边(2->4)根据公式能知道2号的low值为5,不满足出栈条件,回溯到1,满足DFN[1] = LOW[1]把栈中1号顶点以上的全部顶点出栈,即为一个强连通分量。所以此图的强连通分量即为{6},{5},{1,3,2,4}.


为什么满足DFN=LOW就出栈,因为满足这个等式的顶点它必定是一个强连通分量的根节点。为什么?


假设i顶点满足DFN[i]= LOW[i],而i不是一个强连通分量的根结点,那么i肯定在一个至少有两个结点的强连通分量中,且它是该强连通分量中根节点的后代结点,即存在一条i到它祖先的回路,那么这时候low肯定要变小,就不满足这个等式了,所以满足这个等式的顶点一定是一个强连通分量的根节点。


强连通分量肯定是深度优先搜索树中的一颗子树,现在找到了根节点,只要从叶子结点开始一个个拿出强连通分量即可。怎么拿?


注意到首先完成DFS的肯定是叶子结点,那么只要判断叶子结点是否有到祖先结点的边,就可以判断该叶子结点是不是强连通分量,如果没有,证明该叶子结点就是一个强连通分量(这里肯定满足DFS=LOW),拿出即可;如果有,那么把该叶子结点和与它相连的祖先结点看成一个大的叶子结点,继续上述判断即可。


3.强连通分量---Gobow算法


这个算法是Tarjan算法的一种变形,用了两个栈,Stack v和Stack s;Stack v还是用来在DFS中访问结点的存储,只是出栈条件不一样了;Stack s用来存放强连通分量的根节点。两个栈的出栈条件都为“当前结点v = p.top”。

你可能感兴趣的:(C++,算法,遍历,DFS)