算法思想
BFS(广度优先搜索)指的是从起始点开始访问,再访问起始点的所有未被访问过的领接点,再以这些起始点的已被访问过的所有邻接点为起始点进行下一层访问。依次类推,直到图中所有顶点都被访问过为止。
广度优先搜索是一种分层的查找过程,每向前走一步可能访问一批顶点,不像深度优先搜索那样有往回退的情况,因此它不是一个递归的算法。为了实现逐层的访问,算法必须借助一个辅助队列,以记录正在访问的顶点的下一层顶点。
算法模板
条件:
1、判重数组(绝大多数入队时判重,保证每个点只入队一次)
(注:只有A*算法和Dijkstra算法出队时判重)
2、队列queue
模板:
queue <- 初始状态 // 初始状态入队
while(queue非空) { // 当queue不空时
t <- 队头 // 取出队头元素存入t
for(拓展t) { // 拓展t节点
if(!st[ver]) { // 判断新节点是否已经存在
ver -> 队尾 // 将新节点(ver)插到队尾
}
}
}
可以用一句话概括BFS的实现:每次取出队头元素,将拓展出的所有元素放到队尾。
算法应用
一般的BFS算法可分为两种:
(1)每个元素为一个状态,比如走迷宫问题状态转换是下一步往哪走。bfs相对于与dfs可以找到走迷宫的一个最短路径(按层遍历时第一次到达终点时即为最短路径)
例题1 AcWing 1101. 献给阿尔吉侬的花束(属于第一类)
思路:思路即简单的BFS遍历,主要说明以下BFS函数的实现:先将start起点插入队尾,当queue队列中还存在元素时进行while循环,取出队头元素,用偏移量判断可走的方格。如果可以走该方格,则更新当前距离并将该方格入队进行下一次循环。
代码:
#include
#include
#include
#include
#define x first
#define y second
using namespace std;
typedef pair PII;
const int N = 210;
int n, m;
char map[N][N];
int dist[N][N]; // 既判重也表示距离
int bfs(PII start, PII end) {
queue q;
memset(dist, -1, sizeof dist);
dist[start.x][start.y] = 0;
q.push(start);
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1}; // 偏移量
while(q.size()) {
auto t = q.front();
q.pop();
for(int i = 0; i < 4; i ++) {
int x = t.x + dx[i];
int y = t.y + dy[i];
if(x < 0 || x >= n || y < 0 || y >= m) {
continue; // 出界
}
if(map[x][y] == '#') {
continue; // 碰到内墙
}
if(dist[x][y] != -1) {
continue; // 之前遍历过
}
// 以上if皆判false说明接下来方向可走
dist[x][y] = dist[t.x][t.y] + 1; // 更新当前距离
if(end == make_pair(x, y)) {
return dist[x][y];
}
q.push({x, y});
}
}
return -1;
}
int main() {
int T;
scanf("%d", &T);
while(T --) {
scanf("%d %d", &n, &m);
for(int i = 0; i < n; i ++) {
scanf("%s",map[i]);
}
PII start, end; // 起点和终点
for(int i = 0; i < n; i ++) {
for(int j = 0; j < m ; j ++) {
if(map[i][j] == 'S') {
start = {i, j};
} else if(map[i][j] == 'E') {
end = {i, j};
}
}
}
int distance = bfs(start, end);
if(distance == -1) {
printf("oop!\n");
} else {
printf("%d\n", distance);
}
}
return 0;
}
例题2 AcWing 1096.地牢大师(属于第一类)
思路:这一题和上面的那个例题完全一样,只不过多了一个维度而已,为了明确的和上面类似,刻意将pair写成了三个维度的。
代码:
#include
#include
#include
#include
using namespace std;
// 强行写成三维,其实完全可以用struct代替
typedef pair> PIII;
const int N = 110;
int L, R, C;
char map[N][N][N];
int dist[N][N][N];
int dx[6] = {1, -1, 0, 0, 0, 0};
int dy[6] = {0, 0, 1, -1, 0, 0};
int dz[6] = {0, 0, 0, 0, 1, -1};
/*
* x 对应first
* y 对应second.first
* z 对应second.second
*/
int bfs(PIII start, PIII end) {
queue q;
memset(dist, -1, sizeof dist);
dist[start.first][start.second.first][start.second.second] = 0;
q.push(start);
while(q.size()) {
auto t = q.front();
q.pop();
for(int i = 0; i < 6; i ++) {
int x = t.first + dx[i];
int y = t.second.first + dy[i];
int z = t.second.second + dz[i];
if(x < 0 || x >= L || y < 0 || y >= R || z < 0 || z >= C) {
continue;
}
if(map[x][y][z] == '#') {
continue;
}
if(dist[x][y][z] != -1) {
continue;
}
dist[x][y][z] = dist[t.first][t.second.first][t.second.second] + 1;
if(end == make_pair(x, make_pair(y, z))) {
return dist[x][y][z];
}
q.push({x, {y, z}});
}
}
return -1;
}
int main() {
while(scanf("%d%d%d", &L, &R, &C), L || R || C) {
PIII start, end;
for(int i = 0; i < L; i ++) {
for(int j = 0; j < R; j ++) {
scanf("%s", &map[i][j]);
for(int k = 0; k < C; k ++) {
int ch = map[i][j][k];
if(ch == 'S') {
start = {i, {j, k}};
} else if(ch == 'E') {
end = {i, {j, k}};
}
}
}
}
int distance = bfs(start, end);
if(distance == -1) {
printf("Trapped!\n");
} else {
printf("Escaped in %d minute(s).\n", distance);
}
}
return 0;
}
(后续题目待补充)
算法总结
BFS适用于最短路径的解问题,比如最短步数、最短交换次数的解。因为BFS搜索过程中遇到的解一定是离根最近的,所以遇到一个解,一定就是最优解,此时搜索算法可以终止。
缺点是相对于DFS来讲,BFS需要用一个队列存搜索过的状态。