贡献者: 有机物
上文提到了 DFS,本文将介绍与它类似的一种搜索方式:广度优先搜索或者叫宽度优先搜索(Breadth First Search)简称 BFS。
BFS 可以用队列来实现,BFS 的搜索顺序为:当前的状态面临的所有状态优先搜索。
对于一棵搜索树,BFS 不是一个节点一个节点的搜索,而是一层一层的搜索,如果当一个状态第一次入队的时候,那么就是从起始状态到该状态的最小步数,因此 BFS 可以求最小值。对于一张权值都为 的图,可以求得从 号点到 号点的最短距离。
图 1:BFS 的搜索树
BFS 的伪代码:
我们还是看一道例题来学习 BFS:
题意:有一张地图, 表示可以走, 表示不能走,你可以从上下左右四个方向任意一个方向移动一个位置,问你从 走到 最少需要花费多少步?
样例:
图 2:样例的搜索过程
思路:
上图就为 BFS 的搜索顺序,每次搜索队头可以到达的点,然后根据队头更新信息,可以保证答案一定是最小值。先把 号点入队,然后把 号点出队,然后访问队头( 号点)的可以到达的节点,然后在把队头可以到达的节点入队,可以到达的节点的答案为从起点到队头的答案再加从队头到可以到达的节点的步数也就是直接加 。
代码:
图 3:偏移量技巧
可以看到,对于涉及到上下左右方向类的问题,可以不需要写 个 if
判断,我们直接使用两个偏移量,这样可以帮助我们省很多代码。
- 往上走就是横坐标减 ,纵坐标不变。对应:
- 往右走就是横坐标不变,纵坐标加 。对应:
- 往下走就是横坐标加 ,纵坐标不变。对应:
- 往左走就是横坐标不变,纵坐标减 。对应:
让我们来看一道使用偏移量的好题:例题
题目大意:控制一个 小木块,问从起点走到终点最少需要操作多少次,“。” 表示硬地,可以躺着也可以站立着,“E” 表示脆地,只能躺着,“#” 表示障碍物,不能通过。游戏地址。
本题和这款游戏有的操作一模一样,偏移量的值可以根据里面的操作来判断。
做法:
用一个结构体来定义状态,结构体内有三个值,分别为:。 为当前小木块的状态, 表示竖着站立在 上、 表示横着躺,左半部分为 、 表示竖着躺,上半部分为 。
图 4:站立
图 5:横躺
图 6:竖躺
本题的偏移量:
d 数组的第一维(对应上面的行)表示当前的状态是什么,为 表示最开始是立着的、为 表示最开始是横着躺、为 表示最开始是竖着躺。
第二维(对应上面的四大列)表示四个方向,第一大列表示向 “上” 走,第二大列表示向 “右” 走,第三大列表示向 “下” 走,第四大列表示向 “左” 走。
第三维则代表每行内的四个花括号内的元素,第一个元素表示 轴,第二个元素表示是 轴,第三个元素表示状态。
上面的偏移量的更新可以去依据那个游戏,本题的思路较简单,重点就在于上面的偏移量技巧。
代码:
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 510;
struct State
{
int x, y, lie;
};
int n, m;
char g[N][N];
int dist[N][N][3];
State q[N * N * 3];
bool check(int x, int y)
{
if (x < 0 || x >= n || y < 0 || y >= m) return false;
return g[x][y] != '#';
}
int bfs(State, State);
void process(int x, int y)
{
for (int i = 0; i < n; i ++ )
for (int j = 0; j < m; j ++ )
cin >> g[i][j];
State start = {-1, -1, -1}, end;
for (int i = 0; i < n; i ++ )
for (int j = 0; j < m; j ++ )
if (g[i][j] == 'X')
{
if (start.x != -1) continue;
if (g[i][j + 1] == 'X') start = {i, j, 1};
else if (g[i + 1][j] == 'X') start = {i, j, 2};
else start = {i, j, 0};
}
else if (g[i][j] == 'O') end = {i, j, 0};
int res = bfs(start, end);
if (~res) cout << res << endl;
else cout << "Impossible" << endl;
}
int bfs(State start, State end)
{
memset(dist, -1, sizeof dist);
dist[start.x][start.y][start.lie] = 0;
int hh = 0, tt = -1;
q[ ++ tt] = {start};
int d[3][4][3] = {
{{-2, 0, 2}, {0, 1, 1}, {1, 0, 2}, {0, -2, 1}},
{{-1, 0, 1}, {0, 2, 0}, {1, 0, 1}, {0, -1, 0}},
{{-1, 0, 0}, {0, 1, 2}, {2, 0, 0}, {0, -1, 2}},
};
while (hh <= tt)
{
auto t = q[hh ++ ];
for (int i = 0; i < 4; i ++ )
{
State next;
next = {
t.x + d[t.lie][i][0],
t.y + d[t.lie][i][1],
d[t.lie][i][2],
};
int x = next.x, y = next.y;
if (!check(x, y)) continue;
if (next.lie == 0 && g[x][y] == 'E') continue;
if (next.lie == 1 && !check(x, y + 1)) continue;
if (next.lie == 2 && !check(x + 1, y)) continue;
if (dist[x][y][next.lie] == -1)
{
dist[x][y][next.lie] = dist[t.x][t.y][t.lie] + 1;
q[ ++ tt] = {next};
}
}
}
return dist[end.x][end.y][end.lie];
}
int main()
{
while (scanf("%d%d", &n, &m), n || m) process(n, m);
return 0;
}
致读者: 小时百科一直以来坚持所有内容免费无广告,这导致我们处于严重的亏损状态。 长此以往很可能会最终导致我们不得不选择大量广告以及内容付费等。 因此,我们请求广大读者
热心打赏 ,使网站得以健康发展。 如果看到这条信息的每位读者能慷慨打赏 20 元,我们一周就能脱离亏损, 并在接下来的一年里向所有读者继续免费提供优质内容。 但遗憾的是只有不到 1% 的读者愿意捐款, 他们的付出帮助了 99% 的读者免费获取知识, 我们在此表示感谢。