单连通图(算法导论22.3-12)

注意此处的单连通(singleconnected)指任意两个结点u,v,从u到v至多只有一条简单路径。这个问题作为上星题有难度,对于一次DFS来讲,如果存在forward edge则表明必定不是单连通图,但还要考虑cross edge,如果在一个DFS树来讲只要存在cross edge,就表明不是单连通,关键是要处理不同DFS树间的cross edge还有back edge。

 

最简单的实现,从每个点作一次DFS,得到一棵DFS树,如果没有出现DFS树内cross edge和forward edge,则此图必为单连通图。一次DFS复杂度为O(V+E),需要以所有点作一次DFS,故总体复杂度为O(V *(V+E))

这个方法的正确性很好证明,假设图G不是单连通图,即存在两个点u、v,从u到v至少有两条简单路,不妨设两条路径为u,x1,x2,...,xn,v和u,y1,y2,...,ym,v,注意m和n可能有一个为0,从u开始作DFS,根据白色路径定理,这两条路径上的所有点均是u的子孙结点,假设m,n均大于0,xn和ym均是u的子孙结点,v也是u的子孙结点,故(xn,v)和(ym,u)必定有一个是DFS树内的cross edge,当m=0时,n>0,则或者(u,v)为forward edge,或者(xn,v)为DFS树内的cross edge。

 

一直以为此题有线性复杂度的算法,此问题萦绕了近两个月,真是如鲠在喉不吐不快啊。最后在网上查了一通才看到相似的解答,即此问题的通用算法复杂度就是O(V * (V+E)),此图如果是稀疏图(边数较小),则此解法已经无法优化,如果是稠密图(E=O(V*V)),则可以继续优化,以下均假设图是稠密图的情况,这时的复杂度可以控制在O(V*V),相比较通用算法在稠密图的情况O(V * (V+E)) = O(V * V * V),即结点数的立方,本算法会有显著优化。

本算法很多数据结构需要参考强连通分支的Tarjan算法,比如dfn和low,以下先证明Tarjan 算法的某些特性。

引理1:有向图G,Tarjan 算法的DFS过程中不含有cross edge和forward edge,v和v的子结点x,如果有low[x]< dfn[v],则以x为根结点的子树必定有一条back edge,指向v的祖先结点。

证明:由于DFS 过程仅存在backedge,反证法,假设以x为根结点的子树不存在这样的back edge,根据Tarjan算法过程,则low[x]必定等于dfn[x],而已知low[x] < dfn[v] < dfn[x],矛盾,故必定存在这样的back edge

 

引理2:图G的DFS树中不含有forwardedge或cross edge,u是v的祖先结点,则从u到v的任何路径均会经过u到v的DFS树上的所有点

这个引理相当直观,可以对DFS树上u到v的tree edge数目使用数学归纳法严格证明

 

引理3:图G的DFS树中不含有forwardedge或cross edge,如果点u到v有两条完全不同的简单路径(路径上的点各不相同),则v必定为u的祖先结点

证明:DFS图中只有tree edge和back edge,抓住这个重点。u和v只有三种关系

(1) u是v的祖先结点

(2) v是u的祖先结点

(3) u、v没有祖先、子孙关系

只要证明(1)、(3)这两种情况均会出现矛盾即可。

情况(1):根据引理2,u到v的任何路径都要经过DFS树中u到v的所有点,不可能有两条完全不同的路径,故这种情况矛盾,排除。

情况(3): 假设u、v最近公共祖先结点为x,由于没有cross edge和forward edge,则从u到v的任何路径都必须经过x,不可能有两条完全不同的路径,故这种情况矛盾,排除。

 

引理4:图G,从u到v有两条路径,则一定存在两点x,y,从x到y有两条完全不同的路径,即这两个路径上除了起点x、终点y之外,完全不相同

这个引理看起来是很显然的,这里使用构造证明法,假设u到v的一条路径为P1,u, t1, ..., tn=v,另一条路径为P2,u,s1,...,sm=v,从t1到tn这n个点中找一个点ti,保证ti属于路径P2,而且i最小,这个ti点肯定存在,因为点tn就属于P2,只需要找i最小的这样的点

(1) 如果i==n则表示这两条路径完全不同,已经得证

(2) 如果 i>1,则u到ti有两条完全不同的路径

(3) 如果i==1且t1!=s1,则u到t1有两条完全不同的路径

(4) 如果i==1,且t1==s1,则u=t1继续迭代这个过程,由于图G的有限性这个过程一定会终止,又由于这是两条不同的路径,故一定会出现(1) (2) (3)的情况。

 

定理:图G是强连通图且DFS不含有forward edge或cross edge,G不是单连通图当且仅当DFS树满足以下任意一个条件:

(1) 存在点v至少有两条back edge;

(2) 存在点v只有一条back edge,且v的某个子结点x,low[x] <dfn[v]

(3) 存在点v,v至少有两个子结点x,y,low[x] < dfn[v],low[y] <dfn[v]

 

证明:充分性很显然,如果满足任意一个条件,根据引理1,则v到p(v)至少有两条路径,则知G不是单连通图。

必要性比较复杂,假设G不是单连通图,则存在点u、v,从u到v至少有两条路径,又根据引理4,可假设这两条路径完全不相同。又根据引理3则知v必定为u的祖先结点,又DFS只有tree edge和back edge,则以u为根结点的子树必定有back edge到达u的祖先结点,否则从u无法到达v;因为有两条路径,则也必须有两条这样的back edge。

这两条back edge详细划分就分三种情况,

(1) u有两条back edge

(2) u只有一条back edge,另一条back edge在从u为根结点的子树中,且指向u的祖先结点;

(3) u没有back edge,从u为根结点的子树中,有两条backedge指向u的祖先结点

 

此定理可以扩展到含有多个强连通分支的有向图,根据此定理,只要DFS过程不出现任意一个条件,则确保任意一个强连通分支内部是单连通图,然后再考察其分支图即可。

 

单连通图O(V*V)算法步骤

(1) 从任意结点开始进行一次DFS,检查单个强连通分支是否为单连通图。即DFS树没有forward edge、DFS树内cross edge,而且不存在定理3种条件的任意一种。如此确保任意一个强连通分支为单连通图,O(V+E)

(2) 获取分支图GSCC,如果某两个点之间有多条边,则不是单连通图,O(V + E)

(3) 在分支图GSCC基础上,对每一个点进行DFS,由于GSCC基为有向无环图,故其边数必定小于V,则复杂度为O(V*V)

总体复杂度为O(V*V),以下为代码实现

/**********************************************************************
 * singly_connect.c
 *
 * Wang Dongquan <wdq347@163.com>
 * Time-stamp: <>
 * Description: One dfs to judge there are no inter cross, no forward,
 * no chord in one SCC
 ***********************************************************************/

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

#include "graph.h"
#include "list.h"

static int *low = NULL;
static int *dfn = NULL;
static int *color = NULL;

static int DFS_visit_singly(struct link_graph *G, struct link_vertex *u, int root, int *index)
{
    int ret = 0;
    int back_count = 0, child_low = 0;
    struct link_edge *le = NULL;
    
    color[u->vindex] = COLOR_GRAY;
    dfn[u->vindex] = low[u->vindex] = (*index)++;
    
    list_for_each_entry(le, &u->head, node) {
        if (color[le->vindex] == COLOR_BLACK) {
            if (dfn[u->vindex] < dfn[le->vindex]) {
                printf("The G has forward edge\n");
                return -1;      /* forward edge is not permitted in singly-connected graph */
            }
            else if (dfn[root] < dfn[le->vindex]) {
                printf("The G has inter cross edge\n");
                return -1;      /* inter cross edge is not permitted in singly-connected graph */
            }
            /* cross edge over multi DFS tree is allowed */
        }
        else if (color[le->vindex] == COLOR_WHITE) {
            ret = DFS_visit_singly(G, G->v + le->vindex, root, index);
            if (ret)
                return ret;
            low[u->vindex] = MIN(low[u->vindex], low[le->vindex]);
            if (low[le->vindex] < dfn[u->vindex])
                child_low++;
        }
        else {
            back_count++;      /* back edge count */
            low[u->vindex] = MIN(low[u->vindex], dfn[le->vindex]);
        }
    }

    color[u->vindex] = COLOR_BLACK;
    
    if (back_count >= 2) {
        printf("The G has two back edge from u(%d)\n", u->vindex);
        return -1;
    }
    else if (back_count == 1 && child_low >= 1) {
        printf("The G has one back edge from u(%d), and at leas one child_low\n", u->vindex);
        return -1;
    }
    else if (child_low >= 2) {
        printf("The G has at leas two child_low from u(%d)\n", u->vindex);
        return -1;
    }

    return 0;
}

int singly_connect(struct link_graph *G)
{
    int i = 0, ret = 0, index = 0;

    color = malloc(sizeof(int) * G->vcount);
    low = malloc(sizeof(int) * G->vcount);
    dfn = malloc(sizeof(int) * G->vcount);
    
    for (i = 0;i < G->vcount;i++) {
        color[i] = COLOR_WHITE;
    }
    
    for (i = 0;i < G->vcount;i++) {
        if (color[i] == COLOR_WHITE) {
            ret = DFS_visit_singly(G, G->v + i, i, &index);
            if (ret)
                break;
        }
    }
    
    free(color);free(low);free(dfn);

    return ret;
}

代码说明:

(1) 这里只需要获取各个点的low和dfn,不需要获取真正的强连通分支,故没有做Tarjan算法的强连通分支入栈、出栈过程

(2) 当发现DFS树内cross edge时,立即退出,此时不需要按照Tarjan 算法更新low值,因为此算法仅仅判断是否为单连通。

(3) 当发现DFS树间的cross edge时,不需要做任何处理,因为在Tarjan算法中,发现DFS树间的cross edge表明其相邻结点已经出栈了。

(4) 当发现DFS的back edge时,更新当前结点的backedge计数,同时按照Tarjan 算法更新当前结点的low值;

(5) 当发现DFS的tree edge时,按照Tarjan 算法更新当前结点的low值,同时比较子结点和当前结点的low值,计算这类子结点的个数,子结点的low值小于当前结点的dfn值;

(6) 当前结点访问完毕后,即可判断上述三种条件是否成立。


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值