强连通分支之Tarjan 算法

来源:互联网 发布:python爬百度文库 编辑:程序博客网 时间:2024/04/30 01:23

比Kosaraju算法更神奇,只需要一次DFS就可以获得强连通分支。其原理是每一个强连通分支,均包含在DFS树的某个子树中,只要找到这个子树的树根,再逐一将该连通分支所有结点取出即可。

数据结构:

1)        栈,DFS访问结点时入栈,某个强连通分支访问结束后其所有结点出栈,以下需要证明当刚好访问结束某个强连通分支时,其所有结点均在栈顶;

2)        数组dfn,DFS过程中记录每个结点的访问时间,内容一旦赋值不再改变;

3)        数组low,这是个递归定义的值,当某个结点结束访问时固定,记录与该结点相邻且仍然在栈中的结点的low最小值,该值在访问该结点相邻结点后会改变

 

在一个强连通分支中,第一个访问的结点u为其在DFS子树中的树根,此算法保证在这个强连通分支中只有结点u才有dfn[u] = low[u],其他结点没有这个性质。以下简要证明Tarjan 算法的正确性。

 

访问某结点u的算法伪码:

DFS_visit_Tarjan(u)

{

dfn[u] = low[u] = index++;

s[u] = IN_STACK

push(u, stack)

for each (u,v) in E; do    /* 遍历结点u的邻接表 */

if s[v] == NO_STACK

   DFS_visit_Tarjan(v)

   low[u]= MIN(low[u], low[v]);

else if s[u] == IN_STACK

   low[u]= MIN(low[u], dfn[v]);

done

 

if low[u] == dfn[u]

   pop from the stack top to u as one SCC

}

 

引理1:DFS树中如果结点u是结点v的祖先结点,则low[u]<= low[v]

证明:假设u是点v的父结点,根据算法步骤“low[u] = MIN(low[u], low[v])”,则显然有low[u] <=low[v];而u是v的祖先结点,显然也有low[u]<=low[v]

 

引理2:假设某个强连通分支C首个访问的结点u,对于C内其他任意一个结点v,均有low[v] < dfn[v],对于u则有low[u] = dfn[u]

证明:反证法,DFS过程中假设结点v是第一个low与dfn值相同的结点,则以结点v为根的子树中必定没有边到达之前栈中的结点,否则假定有某个结点x,有边(x,y)而且y在结点v之前访问,于是根据算法步骤有low[x] <= dfn[y] < dfn[v] = low[v],但根据引理1,因为v是x的祖先结点,则有low[v]<= low[x],矛盾。与是以结点v为根的子树中必定没有边到达之前入栈的结点,虽然v可以到达其他某个强连通分支C’,但由于分支图的有向无环特性C’不能到达C(因为C可以到达C’),故结点v无法到达结点u,这与强连通分支矛盾,故不会出现这样的结点v,即对于其他任意一个结点v,均有low[v] < dfn[v]。

由于结点u是C中第一个访问的,dfn值在C中最小,而且整个C没有到达之前栈中结点的边,根据Tarjan算法步骤,只有存在这种边时,才能使用low值小于dfn[u],因为没有这种边,故结点的low值一直没变。即dfn[u] = low[u]

由引理2可行,当某个强连通分支C访问完毕时,其所有结点必定都在栈中,此时只要证明栈中从结点u开始没有其他强连通分支的结点即可。

 

 

使用数学归纳法证明Tarjan 算法,当某个DFS过程仅有1个强连通分支时,由引理2确保Tarjan 算法正确;当有n个强连通分支时,假设算法正确,以下证明图G有n+1个强连通分支的情况。

找图G中某个出度为0的强连通分支,由于分支图GSCC为有向无环图,这种强连通分支一定存在,设此强连通分支为C。假设C中最先访问的结点是v,其父结点是u,u属于强连通分支C’。当访问到结点v时,由于C出度为0,由引理2知Tarjan 算法可以完整的取出C,结束结点v访问后,回溯到结点u,此时栈中没有C的结点。

假设从图中删除强连通分支C以及和其相关的所有边得到图G’,按照相同的状态进行DFS,按照归纳假设可以完整的取出n个强连通分支,设其过程为P1。图G的DFS过程为P2,P2对比P1过程仅仅增加了强连通分支C的遍历,在访问C前后结点C访问时,栈中内容没有任何改变,故n+1 个强连通分支Tarjan 算法也可以完整获取。


以下是代码实现

/********************************************************************** * tarjan_scc.c * * Wang Dongquan <wdq347@163.com> * Time-stamp: <> * Description:  ***********************************************************************/#include <stdio.h>#include <time.h>#include <stdlib.h>#include <string.h>#include "list.h"               /* list from Linux_kernel */#include "graph.h"#define MIN(a, b) (((a)<=(b)) ? (a) : (b))#define STACK_WHITE 0           /* not visited */#define STACK_GRAY  1           /* push to the stack */#define STACK_BLACK 2           /* pop from the stack */static void output_tarjan(struct list_head *thead, struct link_vertex *u, int scc_no, int *stack){    struct link_vertex *v = NULL, *tmp = NULL;    printf("The %d SCC:\n", scc_no);        list_for_each_entry_safe(v, tmp, thead, qnode) {        printf("%d ", v->vindex);        stack[v->vindex] = STACK_BLACK;        list_del(&v->qnode);        if (v == u)            break;    }    printf("\n");}void DFS_visit_tarjan(struct link_graph *G, struct link_vertex *u, int *stack, int *low, int *dfn,                      struct list_head *thead, int *scc_no, int *index){    struct link_edge *le = NULL;        stack[u->vindex] = STACK_GRAY;     list_add(&u->qnode, thead);    dfn[u->vindex] = low[u->vindex] = (*index)++;    list_for_each_entry(le, &u->head, node) {        if (stack[le->vindex] == STACK_WHITE) {            DFS_visit_tarjan(G, G->v + le->vindex, stack, low, dfn, thead, scc_no, index);            low[u->vindex] = MIN(low[u->vindex], low[le->vindex]);        }        else if (stack[le->vindex] == STACK_GRAY) { /* already in the stack */            low[u->vindex] = MIN(low[u->vindex], dfn[le->vindex]);        }    }        if (low[u->vindex] == dfn[u->vindex]) {        output_tarjan(thead, u, *scc_no, stack);        *scc_no = *scc_no + 1;    }}int tarjan_find_scc(struct link_graph *G){    int i = 0, scc_no = 0, index = 0;    int *stack = NULL, *low = NULL, *dfn = NULL;    struct list_head tarjan_head; /* stack for tarjan algorithm */    INIT_LIST_HEAD(&tarjan_head);    stack = malloc(sizeof(int) * G->vcount);    low = malloc(sizeof(int) * G->vcount);    dfn = malloc(sizeof(int) * G->vcount);    for (i = 0;i < G->vcount;i++) {        stack[i] = STACK_WHITE;        low[i] = dfn[i] = G->vcount;    }    for (i = 0;i < G->vcount;i++) {        if (stack[i] == STACK_WHITE) {            DFS_visit_tarjan(G, G->v + i, stack, low, dfn, &tarjan_head, &scc_no, &index);        }    }    printf("This graph has %d scc components from tarjan\n", scc_no);    return 0;}