1 - BFS入门
思想
把一些问题抽象成图,从一个点开始,向四周扩散,然后问你求最短路。当边权为1时,才能用BFS。BFS找到的路径一定是最短的,但代价就是空间复杂度可能比DFS大得多。
BFS的代码框架如下:
queue.push(初始状态)
while (队列不空)
{
取队头t
拓展队头t
}
练习题
最短路问题
走迷宫
844. 走迷宫 - AcWing题库
可以从起点(0, 0)开始BFS,每往前走一步,就记录当前位置离起点的距离。这样BFS结束后,终点(n - 1, m - 1)的距离就是离起点的最短距离。
参考代码如下:
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
int n, m;
vector<vector<int>> map(110, vector<int>(110, 0)), dist(110, vector<int>(110, -1));
int bfs()
{
// 初始状态
queue<pair<int, int>> q;
q.push({0, 0});
dist[0][0] = 0;
vector<pair<int, int>> dir = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}};
// while队列不空
while (!q.empty())
{
// 取队头
auto [x, y] = q.front();
q.pop();
// 拓展队头
for (int i = 0; i < 4; ++i)
{
auto [dX, dY] = dir[i];
pair<int, int> next = {x + dX, y + dY};
if (next.first >= 0 && next.first < n &&
next.second >= 0 && next.second < m &&
map[next.first][next.second] == 0 &&
dist[next.first][next.second] == -1)
{
dist[next.first][next.second] = dist[x][y] + 1;
q.push(next);
}
}
}
return dist[n - 1][m - 1];
}
int main()
{
cin >> n >> m;
for (int i = 0; i < n; ++i)
{
for (int j = 0; j < m; ++j)
{
cin >> map[i][j];
}
}
cout << bfs() << '\n';
}
二叉树的最小深度
111. 二叉树的最小深度 - 力扣(LeetCode)
和“最短路径”有关,可以用BFS。最开始将根节点和它的深度1入队,然后开始BFS循环,取队头元素,判断是否是叶子节点(左右子节点均为空),不是的话就入队子节点和它的深度。
参考代码如下:
int minDepth(TreeNode* root) {
if (root == nullptr) return 0;
queue<pair<TreeNode*, int>> q;
q.push({root, 1});
while (!q.empty())
{
// 取队头
auto [cur, depth] = q.front();
q.pop();
// 验证是否是叶子节点
if (cur->left == nullptr && cur->right == nullptr)
return depth;
// 拓展队头
if (cur->left) q.push({cur->left, depth + 1});
if (cur->right) q.push({cur->right, depth + 1});
}
return 1;
}
打开转盘锁
752. 打开转盘锁 - 力扣(LeetCode)
要求打开转盘锁的最小次数,可用BFS来解决,首先看看如何根据问题构造要层序遍历的多叉树:
锁的每个状态可作为多叉树的节点,每次上/下拨视为状态转移。接下来,我们先把工具函数准备好:
// 将转盘锁从左往右第i为下拨
string plus(string str, int i)
{
if (str[i] == '9')
str[i] = '0';
else
++str[i];
return str;
}
string minus(string str, int i)
{
if (str[i] == '0')
str[i] = '9';
else
--str[i];
return str;
}
然后就能写BFS框架了,需要注意的是,如果转出deadends
里的数字,需要跳过本轮BFS。参考代码如下:
string plus(string str, int i)
{
if (str[i] == '9')
str[i] = '0';
else
++str[i];
return str;
}
string minus(string str, int i)
{
if (str[i] == '0')
str[i] = '9';
else
--str[i];
return str;
}
int openLock(vector<string>& deadends, string target) {
// 使用Hash存储deadends, 进行O(1)查询节省时间
unordered_set<string> visited;
visited.insert(deadends.begin(), deadends.end());
// 直接得出答案的情况
if (visited.count(target)) return -1;
if (target == "0000") return 0;
// BFS
queue<pair<string, int>> q;
q.push({"0000", 0});
while (!q.empty())
{
// 取队头
auto [cur, ans] = q.front();
q.pop();
// 验证是否是答案
if (cur == target)
return ans;
// 转到deadend里的数字就放弃BFS
if (visited.count(cur))
continue;
// 拓展队头
visited.insert(cur);
for (int i = 0; i < 4; ++i)
{
string tmp = plus(cur, i);
if (!visited.count(tmp))
q.push({tmp, ans + 1});
tmp = minus(cur, i);
if (!visited.count(tmp))
q.push({tmp, ans + 1});
}
}
return -1;
}
八数码
845. 八数码 - AcWing题库
和上面的的开锁问题同理,但这里有个难点,就是如何用一行字符串存储3x3棋盘:
- 首先找到目标字符在字符串中的索引。
- 在3x3棋盘中,x = 索引 / 3,y = 索引 % 3
- 索引 = x * 3 + y
参考代码如下:
#include <iostream>
#include <vector>
#include <queue>
#include <unordered_map>
using namespace std;
string start = "", target = "12345678x";
unordered_map<string, int> dist;
queue<string> q;
vector<pair<int, int>> dir = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
int main()
{
char ch;
while (cin >> ch)
start += ch;
// BFS
q.push(start);
dist[start] = 0;
while (!q.empty())
{
string cur = q.front();
q.pop();
int dis = dist[cur];
if (cur == target)
{
cout << dis;
return 0;
}
// 拓展队头: 让x上下左右四个方向分别与x交换
int idx = cur.find('x');
int x = idx / 3, y = idx % 3;
for (int i = 0; i < 4; ++i)
{
auto [dx, dy] = dir[i];
int a = x + dx, b = y + dy;
if (a >= 0 && a < 3 && b >= 0 && b < 3)
{
swap(cur[idx], cur[a * 3 + b]);
if (!dist.count(cur))
{
dist[cur] = dis + 1;
q.push(cur);
}
swap(cur[idx], cur[a * 3 + b]);
}
}
}
cout << -1;
}
其他
填充每个节点的下一个右侧节点指针
116. 填充每个节点的下一个右侧节点指针 - 力扣(LeetCode)
117. 填充每个节点的下一个右侧节点指针 II - 力扣(LeetCode)
是一道经典的二叉树的层序遍历题,应使用BFS。每次通过层序遍历,就能得到一层的二叉树节点,我们只需在用当层节点拓展下层节点的同时,将当层节点用链表连接起来就好了。
参考代码如下:
Node* connect(Node* root) {
if (root == nullptr)
return nullptr;
vector<Node*> curLevel;
curLevel.push_back(root);
while (!curLevel.empty())
{
// 拓展下层所有节点的同时, 将当层节点连起来
vector<Node*> nextLevel;
for (int i = 0; i < curLevel.size(); ++i)
{
// 连接当层节点
if (i)
curLevel[i - 1]->next = curLevel[i];
// 拓展下层节点
if (curLevel[i]->left)
nextLevel.push_back(curLevel[i]->left);
if (curLevel[i]->right)
nextLevel.push_back(curLevel[i]->right);
}
curLevel = nextLevel;
}
return root;
}
参考资料
- 《labuladong的算法笔记》
- 算法基础课 - AcWing