广度/宽度优先搜索(BFS)

前言

广度优先搜索 是最简单的图搜索算法之一, 也是许多重要的图算法的原型。Prime的最小生成树算法和Dijkstra的单源最短路径算法都使用了类似广度优先搜索的思想。
给定图G=(V,E) 和一个可以识别的节点 s,广度优先搜索对图G中的边进行系统性的探索来发现可以从源节点,到达所有的节点。该算法能够计算从源结点s到每个可到达的节点的距离(最小的边数),同时生成一棵“广度优先搜索树”。该树以源结点s为根节点,包括所有可以从s到达的结点。对于每个从源结点s可以到达的结点v,在广度优先搜索树里从结点s到结点v的简单路径所对应的的就是图G中从结点s到结点v的“最短路径”,即包含最少边数的路径,该算法既可以用于有向图也可以用于无向图。
广度优先算法之所以如此得名是因为该算法始终是将已经发现的结点和未发现结点之间的边界,沿其广度方向向外扩展。也就是说,算法需要在发现所有距离源结点s为k的所有结点之后,才会发现距离源结点s为k+1的 其他结点。

图的概念

  • 图(graph) 是一种$\textcolor{Blue}{网状数据} $结构, 图是由非空的顶点集合和一个描述顶点之间的关系的集合组成。
  • 图由顶点和边组成,顶点表示对象,边表示对象之间的连接关系。
  • 边也可以带权值,称为带权值图。

    无向图术语

  • 两个顶点之间如果有边连接,视为两个顶点相邻
  • 相邻顶点间的序列称为路径
  • 起点和终点重合的路径称为圈
  • 顶点连接的边数叫做这个顶点的度

  • 没有圈的连通图,就是树
  • 没有圈的非连通图,就是森林
  • 一棵树的边数等于顶点数-1
  • 边数等于顶点数-1 的连通图,就是树
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
BFS(G,s) \\
for each vertex u \in G.V -{s}\\
	u.color = WHITE\\
	u.d = \infty\\
	u.\pi = NIL\\
s.color = GRAY\\
s.d = 0\\
s.\pi = NIL\\
Q = \emptyset\\
ENQUEUE(Q,s)\\
while Q\neq = \emptyset\\
	u = DEQUEUE(Q)\\
	for each v \in G.Adj[u]\\
		if v.color == WHITE\\
			v.color = GRAY\\
			v.d = u.d+1\\
			v.\pi = u\\
			ENQUEUE(Q,v)\\
	u.color = BLACK\\

广度优先搜索的流程图

实例

POJ3984《迷宫问题》
定义一个二维数组:
int maze[5][5] = {
0, 1, 0, 0, 0,
0, 1, 0, 1, 0,
0, 0, 0, 0, 0,
0, 1, 1, 1, 0,
0, 0, 0, 1, 0,
};
它表示一个迷宫,其中的1表示墙壁,0表示可以走的路,只能横着走或竖着走,不能斜着走,要求编程序找出从左上角到右下角的最短路线。

解题思路

队列是先进后出,后进先出。


对应于题目的输入数组:
0, 1, 0, 0, 0,
0, 1, 0, 1, 0,
0, 0, 0, 0, 0,
0, 1, 1, 1, 0,
0, 0, 0, 1, 0,
把节点定义为(x,y),(x,y)表示数组maze的项maze[x][y]。
于是起点就是(0,0),终点是(4,4)。按照刚刚的思路,手工梳理一遍:
初始条件:
起点Vs为(0,0)
终点Vd为(4,4)
灰色节点集合Q={}
初始化所有节点为白色节点
下面开始广度优先搜索:
1.起始节点Vs变成灰色,加入队列Q,Q={(0,0)}
2.取出队列Q的头一个节点Vn,Vn={0,0},Q={}
3.把Vn={0,0}染成黑色,取出Vn所有相邻的白色节点{(1,0)}
4.不包含终点(4,4),染成灰色,加入队列Q,Q={(1,0)}
5.取出队列Q的头一个节点Vn,Vn={1,0},Q={}
6.把Vn={1,0}染成黑色,取出Vn所有相邻的白色节点{(2,0)}
7.不包含终点(4,4),染成灰色,加入队列Q,Q={(2,0)}
8.取出队列Q的头一个节点Vn,Vn={2,0},Q={}
9.把Vn={2,0}染成黑色,取出Vn所有相邻的白色节点{(2,1), (3,0)}
10.不包含终点(4,4),染成灰色,加入队列Q,Q={(2,1), (3,0)}
11.取出队列Q的头一个节点Vn,Vn={2,1},Q={(3,0)}
12.把Vn={2,1}染成黑色,取出Vn所有相邻的白色节点{(2,2)}
13.不包含终点(4,4),染成灰色,加入队列Q,Q={(3,0), (2,2)}
14.持续下去,知道Vn的所有相邻的白色节点中包含了(4,4)……
15.此时获得了答案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
// BFS.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"

#include <iostream>

using namespace std;

int map[5][5];

//相邻四个节点
int borderUponX[4] = { 0, 0, 1, -1 };
int borderUponY[4] = { 1, -1, 0, 0 };

int front = 0, rear = 1;

struct node {
	int pre;
	int x;
	int y;
} path[100];

void print(int i) {//当前节点
	if (path[i].pre != -1) {//找到前面那个节点
		print(path[i].pre);
		cout << "(" << path[i].x << "," << path[i].y << ")" << endl;
	}
	else {//最前面的那个节点
		cout << "(" << path[i].x << "," << path[i].y << ")" << endl;
	}
}

void bfsSearch(int x, int y) {
	//开始节点(出发),前面没有节点了
	path[front].x = x;
	path[front].y = y;
	path[front].pre = -1;

	//当front == rear的时候说明已经走完了所以“相邻”节点
	//且都不通
	while (front < rear) {
		for (int i = 0; i != 4; i++) {
			//相邻节点坐标
			int pathX = path[front].x + borderUponX[i];
			int pathY = path[front].y + borderUponY[i];

			//不符合的节点(遇到边界或已经走过了)
			if (pathY < 0 || pathX < 0 || pathX > 4 || pathY > 4 || map[pathX][pathY])
				continue;
			else {//将front的相邻的可以过去的并且是还没有走过的节点加到路径里面
				map[pathX][pathY] = 1;
				path[rear].x = pathX;
				path[rear].y = pathY;
				path[rear].pre = front;
				rear++;
			}
			if (pathX == 4 && pathY == 4) {
				//找到了一条路径,又是第一次找到
				//那么就是最短路径了
				print(rear - 1);
				break;
			}
		}
		front++;
	}
}

int main(int argc, char const *argv[])
{
	for (int i = 0; i < 5; i++)
		for (int j = 0; j < 5; j++)
			cin >> map[i][j];

	bfsSearch(0, 0);
	system("pause");
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值