强连通分支及kosaraju算法

图论中最重要的结构,很多图论问题都可以转化为强连通分支来降低处理复杂度。一个强连通分支中所有的点都是互相连通的,可以将其收缩为单个点,以此来简化图的处理。强连通分支中的点集合是一个最大集合,即再加入任何一个其他点都会导致不连通。

 

引理1:图G的两个强连通分支C、C’,如果存在点u属于C,u’属于C’,使得(u,u’)为G的一条边,则一定不存在另一条边(v’,v),使得v’属于C’,v属于C。

反证法,假设存在这样的一条边(v’,v),则强连通分支C和C’是互相连通的,即属于一个强连通分支,矛盾。由此可以有分支图的概念

 

定义:图G的分支图GSCC=(VSCC,ESCC),将图G的每一个强连通分支看作一个点,这个点属于VSCC,即分支图GSCC的点数为图G的强连通分支数目,不妨设G的强连通分支为C1,C2,...,Ck,则分支图的点集GSCC为v1,v2,...,vk;定义边(vi,vj)属于ESCC,当且仅当G中有一条边(x,y),且x属于Ci,y属于Cj,由引理1保证此边的方向是唯一的。

 

引理2:图G的分支图GSCC是一个有向无环图(DAG)

反证法,与引理1证明类似,假设存在一个环,则这个环上的所有的强连通分支均互相可达,与多个强连通分支矛盾。故分支图GSCC是一个有向无环图

 

习题22.5-1 增加一条边对图G的强连通分支个数会有什么影响?

假设增加一条边(u,v),u属于强连通分支Ci,v属于强连通分支Cj,

(1) 如果i==j,即同属于一个强连通分支,则添加这条边对强连通分支个数没有影响;

(2) 如果Cj到Ci没有路径,则添加这条边后对强连通分支个数也没有影响

(3) 如果Cj到Ci存在一条路径,则添加这条边后使用Cj,Ci,...Cj形成一个环,在这个环上的所有强连通分支会变成一个新的连通分支,总的强连通分支个数会减少。

 

定义:图G的转置图GT,将图G中的所有边的方向转向即成GT,严格定义G=(V,E),GT=(V,ET), ET={(u,v):(v,u)属于E}

 

引理3:图GT与G有着相同的强连通分支

假设图G的一个强连通分支Ci,图GT中Ci对应的CiT,此子图仍然是互相连通的,同时也是一个极大的连通子图,否则如果存在一个点u,使得CiT 与u互相连通,则在图G中Ci与u也是互相连通的,矛盾。

由此证明图GT与G的强连通分支相同。

 

习题22.5-4 证明((GT)SCC)T=GSCC

由引理3GT与G的强连通分支相同,则((GT)SCC)T与GSCC有着相同的点集,只要证明其边集也相同即可。

假设边(x,y)属于GSCC,即G存在两个强连通分支Ci,Cj,x属于Ci,y属于Cj,则可知(y,x)必属于(GT)SCC,于是(x,y)必属于((GT)SCC)T

假设边(x,y)属于((GT)SCC)T,则可知(y,x)必属于(GT)SCC,则(x,y)必属于GSCC

 

 

引理4:任何一个强连通分支必定包含于图G的DFS过程中的某一个子树。

证明,一个强连通分支C,在图G的DFS过程中,假设强连通分支C中第一个访问的点是u,则C中其他点均可以从u可达,根据白色路径定理,则C中其余的任何点都是点u的子孙结点,如此以u为树根的子树中一定包含该连通分支C

由引理4可知,从任意结点开始DFS,都会使得任何一个强连通分支必定全部包含于某一个DFS树中,即一个DFS树必定是由若干个强连通分支组成的。同时此引理也是强连通分支Tarjan 算法、Kosaraju的基础

 

DFS过程中有结点的结束时间f(v),这里定义结点集合的结束时间f(U),

f(U) = max{f(u): u属于U},即结点集合中最后完成DFS的结点时间。

 

引理5:图G的任意两个强连通分支C,C’,如果存在一条边(u,v),u属于C,v属于C’,则f(C) > f(C’)

证明:从C和C’这两个集合中第一个访问的结点来考虑

(1) 第一个访问的结点x属于C,则根据白色路径定理,C和C’中其余结点均是u的子孙结点,故结点x最后结束,显然有f(C)> f(C’)

(2) 第一个访问的结点x属于C’,从x必定无法访问到C,所以C’的其余结点均是x的子孙结点f(C’) =f(x),当结点x访问结点时,C中的结点尚未被访问过,故显然有f(C) > f(C’)

这个引理可以看作是有向无环图中拓扑序列的一个性质,即有向无环图存在一条边(u,v),则必须有f(u) > f(v),只要将强连通分支收缩成一个点,即将图G看作其分支图GSCC

对于其转置图GT来讲,结论正好相反,如果存在边(u,v)属于GT,且u、v分属于两个强连通分支C、C’,则有f(C) < f(C’)。

 

Kosaraju算法:第一次DFS获取图G的一个拓扑排序,然后按照拓扑排序的顺序,对图GT进行一次DFS,获得的DFS森林即是不同的强连通分支

此算法需要两次DFS,以下所有的f均针对第一次DFS过程。使用数学归纳法进行证明,假设第二次DFS时前k个DFS树均是强连通分支,在k=0时显然。

由归纳假设,前k个DFS树均是强连通分支,由第二次DFS过程,取余下所有点中的最晚结束的结点u,假设u属于强连通分支C,则对于尚未访问的任何一下其他连通分支C’,有f(u) = f(C) > f(C’),由引理5可知,不存在从C到C’的边,于是从访问u开始,DFS过程访问完C后就结束,恰好是一个完整的强连通分支C

将G看作GSCC再来理解Kosaraju算法,第一次DFS相当于将GSCC作了一次拓扑排序,因为GSCC是有向无环图。然后第二次DFS时,按照GSCC的拓扑逆序对GT进行DFS,相当于以(GT)SCC逆拓扑排序的方式进行DFS,所以每一个DFS树均对应(GT)SCC的一个点集,即G的一个强连通分支,又因为((GT)SCC)T= GSCC,第二次DFS正好是GSCC的拓扑排序,这是Kosaraju算法的一个隐藏性质。

 

这个算法初看起来甚是神奇,简单的两次DFS就可以得到强连通分支,几乎不用其他的数据结构,感谢Kosaraju这个印度人。


最后是代码实现:

#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>

#include "list.h"               /* list from Linux_kernel */
#include "graph.h"

static void print_scc(struct list_head *head, int scc_no)
{
    struct link_vertex *v = NULL;

    printf("The %d SCC:\n", scc_no);
    list_for_each_entry(v, head, qnode) {
        printf("%d ", v->vindex);
    }
    printf("\n");
}

int find_scc(struct link_graph *G)
{
    int *color, i = 0;
    struct link_graph GT;
    struct link_vertex *v = NULL;
    struct list_head topo_head, scc_head;
    
    color = malloc(sizeof(int) * G->vcount);
    for (i = 0;i < G->vcount;i++) {
        color[i] = COLOR_WHITE;
    }

    INIT_LIST_HEAD(&topo_head);
    graph_topo_sort(G, &topo_head);

    printf("\n\nOutput all components of this graph\n\n");
    graph_transpos(G, >);
    i = 0;
    list_for_each_entry(v, &topo_head, qnode) {
        INIT_LIST_HEAD(&scc_head);
        if (color[v->vindex] == COLOR_WHITE) {
            DFS_visit_topo(>, GT.v + v->vindex, color, &scc_head);
            print_scc(&scc_head, i++);
        }
    }
    link_graph_exit(>);
    free(color);
    printf("This graph has %d scc components\n\n", i);

    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值