算法学习笔记(22)——宽度优先搜索BFS
宽度优先搜索 BFS
宽度优先搜索每次扩展当前结点的所有相邻结点,所以需要一个队列来维护要扩展的结点。由于宽度优先搜索每次都是把所有能到的下一步搜完,所以能够得到最短路径的解,所以一些不带权求最短路径的问题也可以直接用BFS解决。
注意,在扩展结点的某个下一结点(也就是把这个下一结点加入到队列)的时候,要保证这个结点是没被扩展过的,否则队列里就有重复结点了,就会出现重复访问了。
宽搜思路:
queue <= 初始化
while queue不空 {
t <= 取出队头元素
拓展 t
}
走迷宫
这个就是一个不带边权的最短路问题,直接BFS搜一下就行了,遇到墙或者已经扩展过的点就不用扩展了,核心在与到扩展的点的最短距离,等于到当前点的最短距离+1,也就是dist[a][b] = dist[x][y] + 1
。
注意这里用dist
中某个点值为-1
表示这个点没有访问过,注意边界:到(0,0)
位置自己的距离是0
。
#include <iostream>
#include <queue>
#include <cstring>
using namespace std;
typedef pair<int, int> PII; // pair类型用于存储点的横纵坐标
const int N = 110;
int n, m;
int g[N][N], d[N][N]; // g存储图,d存储每个点到起点的距离
queue<PII> q; // 用于宽搜的队列,存储点的坐标
int bfs()
{
q.push({0, 0}); // 将起点入队
memset(d, -1, sizeof d); // 初始化距离数组为-1,代表不可达
d[0][0] = 0; // 将起点的距离更新为0
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.first + dx[i], y = t.second + dy[i];
// 判断坐标合法性,若第一次到达(x, y),且该点可达,则更新距离,并将该点入队
if (x >= 0 && x < n && y >= 0 && y < m && d[x][y] == -1 && g[x][y] == 0) {
d[x][y] = d[t.first][t.second] + 1;
q.push({x, y});
}
}
}
return d[n - 1][m - 1]; // 返回(n,m)点到起点的距离(坐标从0开始)
}
int main()
{
cin >> n >> m;
for (int i = 0; i < n; i ++ )
for (int j = 0; j < m; j ++ )
cin >> g[i][j];
cout << bfs() << endl;
return 0;
}
八数码
这个问题就是把整个九宫格的局面当成一个节点,给定一个局面,就能够知道如何转移到其它局面,以及给出了目标局面,要求最短转移是多少步:
所以要想个方法来记录局面,可以从上到下从左到右记录到一个字符串里(就相当于二维字符数组展品成了一维),由于知道行数为3,列数为3,可以很方便的对两者的下标进行转换。
另外要记录到每个结点的距离,由于这里的结点是局面(字符串),不能像上一个题一样开个数组记录了,所以可以用哈希表来记录。
其它的和上一题没有本质区别,都是求无权最短路的问题,直接BFS。
#include <iostream>
#include <queue>
#include <unordered_map>
using namespace std;
// 方向坐标变换数组
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
int bfs(string start)
{
string end = "12345678x"; // 定义终止状态
queue<string> q; // 存储队列中的状态
unordered_map<string, int> d; // 存储每个状态对应的交换次数
q.push(start); // 将初始状态入队
d[start] = 0; // 初始状态需要的交换次数为0
// BFS遍历过程
while (q.size()) {
auto t = q.front(); // 取出队头元素
q.pop();
if (t == end) return d[t]; // 到达目标状态,则返回距离
int distance = d[t]; // 之后会利用t进行字符交换,所以预存t状态的交换次数备用
int k = t.find('x'); // 找到t中字符'x'的下标
int x = k / 3, y = k % 3; // 将一维下标转换为二维形式
// 针对每一状态,遍历x的上下左右四个方向
for (int i = 0; i < 4; i ++ ) {
int a = x + dx[i], b = y + dy[i];
if (a >= 0 && a < 3 && b >= 0 && b < 3) {
swap(t[a * 3 + b], t[k]); // 交换x与目标方向的字符
if (!d.count(t)) { // 若该状态第一次到达,则更新距离,并入队
d[t] = distance + 1;
q.push(t);
}
swap(t[a * 3 + b], t[k]); // 恢复t的状态,预备下一个方向的交换
}
}
}
return -1; // 宽搜后没有找到目标状态,返回-1
}
int main()
{
string start;
for (int i = 0; i < 9; i ++ ) {
char ch;
cin >> ch;
start += ch;
}
cout << bfs(start) << endl;
return 0;
}