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;
}