BFS 最短路径证明及实现

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

【BFS 最短路径证明及实现】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_GRAY1 #define COLOR_BLACK 2if (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; #endiffor (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; }




    推荐阅读