BFS 最短路径证明及实现

BFS最短路径感觉是显而易见的,但证明却颇费工夫,以下证明大部分摘自CLRS,使用倒序形式进行证明比较好理解。首先需要证明一条引理,即BFS中所有点的d值按照入队列成升序排列,即d(s) <= d(v1) <= ... <=d(vr)。

 

1. BFS得到的是一条路径,即从起始点s到任意一点v的路径d(v),因此它必定大于等于最短路径δ(s,v),即有d(v) >=δ(s,v)

 

2. 以下只需要证明d(v)>δ(s,v)情况不存在即可,故假设d(v) >δ(s,v)再得到矛盾。

 

使用数学归纳法进行证明d(v) =δ(s,v),对δ(s,v)的长度进行归纳证明,当δ(s,v)=0时显然成立,当且仅当s=v;当δ(s,v)=1时也成立,这些v必定和s直接相连,故由DFS的过程可知,这些d(v)均等于1,故也成立;以下假设对长度为δ(s,v)<=n时均成立。

当δ(s,v)=n+1时,即s到v的最短路径长度为n+1,假设经过BFS得到d(v) >δ(s,v),设s到v的最短路径中u为v的上一个结点,由最短路径的定义可知δ(s,u) =δ(s,v) – 1 = n,由以上的归纳假设可知δ(s,u)=d(u),即有如下不等式:

d(v) >δ(s,v) = d(u)+ 1

 

当点v出队列时,点u只有三种情况,白色、灰色、黑色,以下证明这三种情况均会导致矛盾。

(1) 点u白色,由于点u、v相邻,根据BFS过程,d(u) =d(v) + 1,于d(v) > d(u) + 1矛盾;

(2) 点u灰色,则表明在处理某个点w时将u置为灰色,由于w已经出队列,根据引理有d(w)<=d(u),而d(v) = d(w)+1,则有d(v) <= d(u) + 1,矛盾;

(3) 点u黑色,则表明点u在v之前出队列,故d(u)<=d(v),也有矛盾。

由此可知BFS 确实是最短路径。

 

引理:BFS处理过程中,假设队列Q中的点为 v1, v2, ..., vr,其中v1是队列头,升序排列,即d (v1)<=d(v2)<= ...<=d(vr),而且d (v1) +1 >= d(vr),即最多只有两个d值

 

使用数学归纳法进行证明,当刚开始只有点s时,命题显然。然后证明当某个点出队列、入队列这两个过程均不会改变这两种属性即可。

1. 出队列过程,由于归纳假设d(v1)+ 1>=d(vr),且为升序,点v1出队列,v2成为队列头,升序这个属性不会改变,而且d(v2) + 1>=d(v1) + 1>=d(vr),故命题依然成立;

2. 入队列过程,假设新入队列的点为vr+1,则vr+1必定由v1之前的点u加入,即d(v1) >=d(u),d(vr+1) = d(u) + 1,当点u出队列以前,Q队列中有r+1个点,u,v1,v2, ..., vr,根据归纳假设,d(u) + 1>=d(vr),于是有d(vr+1)>= d(vr),即队列的单调升序属性依然存在。d(v1) + 1>=d(u)+1= d(vr+1),故引理得证。


树结构使用邻接表进行存储,使用linux_kernel 中的链表操作,如下:

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

struct link_vertex {            /* vertex type */
    int vindex;
    struct list_head head;      /* linked to all edges */
    struct list_head qnode;     /* used for Queue when BFS */
};

struct link_edge {              /* edge type */
    struct list_head node;
    int vindex;
    int weight;
};

struct link_graph {
    int vcount;
    int ecount;
    struct link_vertex *v;
};

BFS过程相当明显,和CLRS中的伪码极其相似

void print_path(struct link_graph *G, int s, int v, struct link_vertex **pi)
{
    if (v == s)
        printf("%d ", v);
    else {
        if (pi[v] == NULL)
            printf("no path from %d to %d exists\n", s, v);
        else {
            print_path(G, s, pi[v]->vindex, pi);
            printf("%d ", v);
        }
    }
}


int BFS(struct link_graph *G, int vindex)
{
    int *color, *d, i = 0;
    struct link_vertex **pi;
    struct list_head queue;
    struct link_vertex *v = NULL;

#define COLOR_WHITE 0
#define COLOR_GRAY  1
#define COLOR_BLACK 2
    
    if (vindex >= G->vcount)
        return -1;
    color = malloc(sizeof(int) * G->vcount);
    d = malloc(sizeof(int) * G->vcount);
    pi = malloc(sizeof(struct link_vertex *) * G->vcount);

    for (i = 0;i < G->vcount;i++) {
        v = G->v + i;
        color[i] = COLOR_WHITE;
        d[i] = -1;
        pi[i] = NULL;
    }

    color[vindex] = COLOR_GRAY;
    d[vindex] = 0;
    pi[vindex] = NULL;
    INIT_LIST_HEAD(&queue);

    v = G->v + vindex;
    list_add_tail(&v->qnode, &queue);

    while (!list_empty(&queue)) {
        struct link_edge *lv = NULL;
        
        v = list_entry(queue.next, struct link_vertex, qnode);
        list_del(&v->qnode);
        
        list_for_each_entry(lv, &v->head, node) {
            if (color[lv->vindex] == COLOR_WHITE) {
                color[lv->vindex] = COLOR_GRAY;
                pi[lv->vindex] = v;
                d[lv->vindex] = d[v->vindex] + 1;
                list_add_tail(&(G->v + lv->vindex)->qnode, &queue);
            }
        }
        color[v->vindex] = COLOR_BLACK;
    }

    printf("\nThe path from %d to %d\n", vindex, vindex + 1);
    print_path(G, vindex, (vindex + 1) % G->vcount, pi);
    printf("\n");

    free(pi);
    free(d);
    free(color);

    return 0;
}

随机生成一个邻接矩阵形式的图,然后再将这个邻接矩阵转化为邻接表存储,以下代码可以直接复制测试(要注意必须将linux_kernel 的链表操作list.h 包含进来)

static int link_edge_init(struct link_graph *G, struct link_vertex *v, int vcount, int *weight)
{
    int i = 0;
    struct link_edge *lv = NULL;

    for (i = 0;i < vcount;i++) {
        if (v->vindex == i || weight[i] == 0)
            continue;
        lv = malloc(sizeof(struct link_edge));
        if (lv == NULL)
            return -1;

        lv->vindex = i;
        lv->weight = weight[i];
        list_add(&lv->node, &v->head);
        G->ecount++;
    }

    return 0;
}


int link_graph_init(struct link_graph *G, int vcount, int *weight)
{
    int i = 0;
    struct link_vertex *v = NULL;

    G->ecount = 0;
    G->vcount = vcount;
    G->v = malloc(sizeof(struct link_vertex) * vcount);
    if (G->v == NULL) {
        printf("OOM for G->v\n");
        return -1;
    }

    for (i = 0;i < vcount;i++) {
        v = G->v + i;
        v->vindex = i;
        INIT_LIST_HEAD(&v->head);
        link_edge_init(G, v, vcount, weight + i * vcount);
    }

    return 0;
}


void link_graph_exit(struct link_graph *G)
{
    int i = 0;
    struct link_vertex *v = NULL;
    struct link_edge *lv = NULL, *tmp = NULL;
    
    for (i = 0;i < G->vcount;i++) {
        v = G->v + i;
        list_for_each_entry_safe(lv, tmp, &v->head, node) {
            free(lv);
        }
    }

    free(G->v);
}


void link_graph_print(struct link_graph *G)
{
    int i = 0;
    struct link_vertex *v = NULL;
    struct link_edge *lv = NULL;

    printf("G has %d vertexes and %d edges\n", G->vcount, G->ecount);
    for (i = 0;i < G->vcount;i++) {
        v = G->v + i;        
        printf("vindex, %4d\n", v->vindex);
        list_for_each_entry(lv, &v->head, node) {
            printf("[%4d,%4d], ", lv->vindex, lv->weight);
        }
        printf("\n");
    }
}


int main(int argc, char **argv)
{
    int i, j;
    int n = 10, r = 0;
    int **matrix = NULL, *p = NULL;
    struct link_graph G;

    if (argc >= 2) {
        n = strtoul(argv[1], 0, 0);
    }

    p = malloc(n * n * sizeof(int));
    memset(p, 0, n * n * sizeof(int));
    matrix = malloc(n * sizeof(int *));
    for (i = 0;i < n;i++) {
        matrix[i] = p + i * n;
    }

#if 1
    srand(time(0));
    for (i = 0;i < n;i++) {
        for (j = 0;j < n;j++) {
            r = rand();
            if ((r & 0xf) == 0)
                matrix[i][j] = 1;
        }
    }
#else
(void)r;
    matrix[0][0] = matrix[0][1] = matrix[0][2] = matrix[0][3] = 1;
    matrix[1][2] = matrix[1][3] = 1;
    matrix[2][0] = matrix[2][1] = 1;
    matrix[3][0] = 1;
#endif

    for (i = 0;i < n;i++) {
        for (j = 0;j < n;j++) {
            printf("%4d ", matrix[i][j]);
        }
        printf("\n");
    }

    link_graph_init(&G, n, p);
    link_graph_print(&G);

    BFS(&G, 0);

    link_graph_exit(&G);
    free(matrix);
    free(p);

    return 0;
}


你可能感兴趣的:(算法导论相关)