总体描述
BFS 是最简单的图搜索算法之一。
Prim最小生成树算法和Dijkstra的单源最短路径算法都使用了类似BFS的思想。
给定图G=(V, E)和一个可以识别的源节点s, BFS能够计算出从源点s到每个可以到达的节点的距离(最少的边数),同时生成一颗“BFS生成树“。
BFS的搜索过程是沿着其广度方向扩展的,算法需要在发现了所有距离源点s为k的所有节点之后,才会发现距离源节点s为k+1的其他节点。
为了跟踪算法进展,BFS在概念上将每个节点标识好颜色(白色:开始状态; 灰色:被发现,但是其邻接点还没有更新完,即邻接点还有白色的; 黑色:被发现,邻接点完全更新完)。
BFS对灰色和黑色节点加以区别,以确保搜索按照BFS模式进行。
执行BFS的过程,我们将构造出一颗BFS优先树。
最后打印BFS的路径时,我们可以根据生成树的“前驱-后继“状态来打印。
搜索过程:
假定输入图G = (V, E) 是以邻接链表所表示的,该算法为图中每个节点赋予了一些额外属性: 将每个节点u的颜色存放在属性u.color里,将u的前驱节点存放在属性u.pre之中,没有就记录为NIL。使用一个FIFO队列Queue管理灰色节点。
伪代码如下(BFS过程,构造BFS树):
BFS (G, s)
{
for each vertex {u} belongs to (G.V - {s})
u.color = WHITE
u.d = INF
u.pre = NIL
s.color = GRAY
s.d = 0
s.pre = NIL
Q = NULL 空队列
ENQUEUE(Q, s) //s入队
while (Q != NULL) {
u = DEQUEUE(Q) //出队
for each {v} belongs to (G.Adj[u]) //u的所有邻接节点
{
if v.color == WHITE {
v.color = GRAY
v.d = u.d + 1
v.pre = u
ENQUEUE(Q, v) //入队
}
}
u.color = BLACK
}
}
打印BFS路径,在BFS构造树之后:
PRINT-PATH(G, s, v)
{
if v == s
print s
else if v.pre == NIL
print "no path from " s " to " v "exists"
else
PRINT-PATH(G, s, v.pre)
print v
}
时间复杂度分析
邻接表存储时,BFS搜索的总运行时间为 O(V + E)
实践
按照伪代码写demo,使用了邻接表存储,循环数组模拟队列即可。图是按照算法导论上的图 P345
代码如下:
bfs.h
#include <stdio.h>
#include <stdlib.h>
#define INF 10000
#define NIL 0
#define MAX 100
#define DATA char
enum judge{
white = 0,
gray = 1,
black = 2
};
struct node {
DATA name;
DATA pre_node;
size_t distance;
enum judge color;
};
struct G {
struct node *array;
struct vexnode *aja_table;
struct graph *g;
};
// ===================
struct graph {
size_t nodes_number;
size_t edges_number;
};
struct vexnode {
DATA id;
struct arcnode *arc_head;
};
struct arcnode {
DATA id;
struct arcnode *next;
};
bfs.c
#include "./bfs.h"
struct graph *generate_graph()
{
struct graph *head = NULL;
if ((head = (struct graph *)malloc(sizeof(struct graph))) == NULL)
return NULL;
head->nodes_number = 0;
head->edges_number = 0;
return head;
}
struct arcnode *malloc_arcnode(const DATA id)
{
struct arcnode *tmp = NULL;
if ((tmp = (struct arcnode *)malloc(sizeof(struct arcnode))) == NULL)
return NULL;
tmp->id = id;
tmp->next = NULL;
return tmp;
}
struct vexnode *malloc_vexnode(size_t length, struct graph *g)
{
struct vexnode *head = NULL;
if ((head = (struct vexnode *)malloc(length * sizeof(struct vexnode))) == NULL)
return NULL;
g->nodes_number = length;
return head;
}
void generate_vexnode(struct vexnode head[], struct graph *g)
{
/*
* 做测试,id就用ABCDEFGH
*/
char data = 'A';
size_t length = g->nodes_number;
struct vexnode *iter = head;
for (size_t i = 1; i <= length; i++) {
iter[i].id = data;
iter[i].arc_head = NULL;
data++;
}
}
struct vexnode *search_vexnode_by_id(struct vexnode head[], const DATA id, struct graph *g) {
size_t length = g->nodes_number;
struct vexnode *first = head;
for (size_t i = 0; i <= length; i++) {
if (first[i].id == id)
return (first + i);
}
return NULL;
}
int judge_arcnode_by_i_j(struct vexnode head[], const DATA i, const DATA j, struct graph *g)
{
struct vexnode *result = search_vexnode_by_id(head, i, g);
if (result == NULL)
return -1; //failure
struct arcnode *iter = result->arc_head;
while (iter != NULL) {
if (iter->id == j) {
return 0; //success
}
iter = iter->next;
}
return -2; //not find
}
int add_arcnode_by_i_j(struct vexnode head[], const DATA i, const DATA j, struct graph *g)
{
int judge = -3;
if ((judge = judge_arcnode_by_i_j(head, i, j, g)) == 0) {
printf("already exist.\n");
return -1;
}
struct vexnode *search = NULL;
search = search_vexnode_by_id(head, i, g);
if (search == NULL) {
printf("could not search.\n");
return -1;
}
struct arcnode *insert = malloc_arcnode(j);
insert->next = search->arc_head;
search->arc_head = insert;
g->edges_number++;
return 0;
}
struct node *search_node_by_name(struct G *total, const DATA name)
{
size_t length = total->g->nodes_number;
for (size_t i = 0; i < length; i++) {
if (name == total->array[i].name) {
struct node *position = total->array + i;
return position;
}
}
return NULL;
}
void BFS(struct G *total, const DATA s)
{
struct node *data_head = total->array;
size_t length = total->g->nodes_number;
for (size_t i = 0; i < length; i++) {
if (data_head[i].name == s) {
continue;
}
data_head[i].distance = 0;
data_head[i].pre_node = NIL;
data_head[i].color = white;
}
struct node *the_start = total->array;
while (the_start->name != s) {
the_start++;
}
if (the_start == NULL) {
printf("failed to find the start point.\n");
return ;
}
the_start->distance = 0;
the_start->pre_node = NIL;
the_start->color = gray;
// begin
DATA Queue[MAX] = {0};
size_t top, end; //top做入队, end做出队
top = end = 0;
Queue[top] = s; // enqueue
top = (top + 1) % MAX;
while (top != end) { // Queue is not empty
DATA u = Queue[end]; //出队
end = (end + 1) % MAX;
printf("start: u = %c\n ", u);///////////////////////
struct node *search_u = NULL;
if ((search_u = search_node_by_name(total, u)) == NULL) {
printf("search u error\n");
return;
}
struct vexnode *ajacency = search_vexnode_by_id(total->aja_table, u, total->g);
struct arcnode *arc_start = ajacency->arc_head;
//printf("search vexnode by id: find %c\n", ajacency->id);
//getchar();
while (arc_start != NULL) {
DATA v = arc_start->id;
struct node *search_v = search_node_by_name(total, v);
if (search_v == NULL) {
printf("search v error\n") ;
return ;
}
if (search_v->color == white) {
search_v->distance = search_u->distance + 1;
search_v->pre_node = search_u->name;
search_v->color = gray;
Queue[top] = v; //enqueue
top = (top + 1) % MAX;
printf("enqueue v = %c\n", v);
}
arc_start = arc_start->next;
}
//getchar();
}
}
void print_bfs(struct G *total, const DATA s, const DATA v)
{
struct node *search_v = search_node_by_name(total, v);
if (search_v == NULL) {
printf("print_bfs: could not find v\n") ;
return;
}
if (s == v)
printf("%c ", s);
else if (search_v->pre_node == NIL)
printf("no path from %c to %c \n", s, v);
else {
print_bfs(total, s, search_v->pre_node) ;
printf("%c ", v);
}
}
int main(void)
{
struct graph *g = generate_graph();
struct vexnode *head = malloc_vexnode(8, g);
generate_vexnode(head, g);
// add data here
add_arcnode_by_i_j(head, 'A', 'B', g);
add_arcnode_by_i_j(head, 'B', 'A', g);
add_arcnode_by_i_j(head, 'B', 'C', g);
add_arcnode_by_i_j(head, 'C', 'B', g);
add_arcnode_by_i_j(head, 'A', 'D', g);
add_arcnode_by_i_j(head, 'D', 'A', g);
add_arcnode_by_i_j(head, 'D', 'E', g);
add_arcnode_by_i_j(head, 'E', 'D', g);
add_arcnode_by_i_j(head, 'D', 'F', g);
add_arcnode_by_i_j(head, 'F', 'D', g);
add_arcnode_by_i_j(head, 'E', 'F', g);
add_arcnode_by_i_j(head, 'F', 'E', g);
add_arcnode_by_i_j(head, 'E', 'G', g);
add_arcnode_by_i_j(head, 'G', 'E', g);
add_arcnode_by_i_j(head, 'F', 'G', g);
add_arcnode_by_i_j(head, 'G', 'F', g);
add_arcnode_by_i_j(head, 'F', 'H', g);
add_arcnode_by_i_j(head, 'H', 'F', g);
add_arcnode_by_i_j(head, 'H', 'G', g);
add_arcnode_by_i_j(head, 'G', 'H', g);
struct G *total = NULL;
if ((total = (struct G *)malloc(sizeof(struct G))) == NULL)
return -1;
total->aja_table = head;
total->g = g;
if ((total->array = (struct node *)malloc(g->nodes_number * sizeof(struct node))) == NULL)
return -1;
//initialize
DATA temp = 'A';
for (size_t j = 0; j < total->g->nodes_number; j++) {
total->array[j].name = temp;
temp++;
}
BFS(total, 'A');
print_bfs(total, 'A', 'G');
return 0;
}
结果, 输入:A~G
A D F G
问题二:
A和B要坐飞机去某个城市,他们现在在一号城市,目标是五号城市,可是一号城市并没有直达五号的航班,B此时已经收集了很多航班信息,现在A希望找到一种乘坐方式,使得转机的次数最少,如何解决呢?
样例输入:
5 7 1 5
1 2
1 3
2 3
2 4
3 4
3 5
4 5
第一行四个数字:节点数、边数、起始点、终止点
然后M行,代表连边
分析:
典型的BFS过程,连边之间的代价当成 1 即可。
BFS过程利用队列,逐层遍历玩去之后再往下进行。
实现:
#include <stdio.h>
#include <stdlib.h>
#define MAX 50
#define NIL 0
typedef enum {
UNKNOWN,
WHITE,
GRAY,
BLACK
}COLOR;
struct node {
COLOR color;
int parent;
int distance;
};
struct node node_accord[MAX + 1]; //记录节点的详细信息
int graph[MAX + 1][MAX + 1]; //记录图的信息
int book[MAX + 1]; //标记数组
int N, M; // 节点数,边数
int start_node, target_node; //起始点,终止点
//模拟队列
int QUEUE[MAX + 1];
int head = 0;
int tail = 0;
//标记,是否找到目的地
int flag = 0;
//从begin点开始
void BFS(int begin) {
for (int i = 1; i <= N; i++) {
node_accord[i].color = WHITE;
node_accord[i].parent = NIL;
node_accord[i].distance = 0;
}
node_accord[begin].color = GRAY;
node_accord[begin].parent = NIL;
node_accord[begin].distance = 0;
book[begin] = 1;
//入队
QUEUE[tail++] = begin;
while (head != tail) {
//出队
int temp = QUEUE[head];
head++;
for (int i = 1; i <= N; i++) {
if (graph[temp][i] == 1 && book[i] == 0) {
book[i] = 1; //book数组标识节点是否遍历过
node_accord[i].parent = temp;
node_accord[i].distance = node_accord[temp].distance + 1;
node_accord[i].color = GRAY;
//入队
QUEUE[tail] = i;
tail++;
if (i == target_node) {
flag = 1; //已经找到了目的地,标记
break;
}
}
}
if (flag == 1) {
break;
}
node_accord[temp].color = BLACK;
}
node_accord[begin].color = BLACK;
}
//递归打印路径
void PRINT_PATH(int start, int end)
{
if (start == end)
printf(" %d ", start);
else if (node_accord[end].parent == NIL)
printf("no path from %d to %d\n", start, end);
else {
PRINT_PATH(start, node_accord[end].parent);
printf(" %d ", end);
}
}
int main()
{
scanf("%d %d %d %d", &N, &M, &start_node, &target_node);
for (int i = 1; i <= M; i++) {
int x, y;
scanf("%d %d", &x, &y);
graph[x][y] = 1;
}
BFS(1);
if (flag == 1) {
//注意是tail - 1的位置
printf("path distance = %d\n", node_accord[QUEUE[tail - 1]].distance);
PRINT_PATH(start_node, target_node); //递归打印路径
printf("\n");
}
else
printf("do not find path..");
return 0;
}
结果:
再测另外一组数据: