BFS算法框架
BFS的核心思想,就是把一些问题抽象成图,从一个节点开始,向四周扩散。一般来说,写BFS都是用[队列]这个数据结构,每次将一个节点周围的节点加入到队尾。
BFS相对于DFS的最主要区别是:BFS找到的路径一定是最短的,但代价就是空间复杂度比DFS大很多。本文从两道经典的BFS题目来讲解。
先举例⼀下 BFS 出现的常⻅场景好吧, 问题的本质就是让你在⼀幅「图」 中找到从起点 start 到终点 target 的最近距离, 这个例⼦听起来很枯燥, 但是 BFS 算法问题其实都是在⼲这个事⼉, 把枯燥的本质搞清楚了, 再去欣赏各种问题的包装才能胸有成竹嘛。
-
⽐如⾛迷宫, 有的格⼦是围墙不能⾛, 从起点到终点的最短距离是多少?如果这个迷宫带「传送门」 可以瞬间传送呢?
-
⽐如说两个单词, 要求你通过某些替换, 把其中⼀个变成另⼀个, 每次只能替换⼀个字符, 最少要替换⼏次?
-
⽐如说连连看游戏, 两个⽅块消除的条件不仅仅是图案相同, 还得保证两个⽅块之间的最短连线不能多于两个拐点。 你玩连连看, 点击两个坐标, 游戏是如何判断它俩的最短连线有⼏个拐点的?
这些问题都没啥奇技淫巧, 本质上就是⼀幅「图」 , 让你从⼀个起点, ⾛到终点, 问最短路径。 这就是 BFS 的本质, 框架搞清楚了直接默写就好。
BFS框架:
int BFS(Node start, Node target){
// 核心数据结果:队列
queue<Node> q;
// 标记集合,避免走回头路
set<Node> visited;
// 首先将起点加入队尾
q.push(start);
// 记录扩散的次数(其实就是要求的路径长度)
int step = 0;
while(!q.empty()){
int sz = q.size();
// 将当前队列中的所有节点向其”周围(图就是邻接点,二叉树就是子节点)“扩散
for(int i = 0; i < sz; i++){
// 获取队首元素并将其出列
Node cur = q.front();
q.pop();
// 划重点,这里判断是否到达终点
if(cur is target)
return step;
// 将当前节点cur的所有相邻节点加入队尾
for(Node p : cur.adj()){
if(p not in visited){
// 加入队尾
q.push(p);
// 标记
visited.add(p);
}
}
}
// 划重点,更新步数在这里
step++;
}
}
其中,cur.adj()
表示节点cur
的相邻节点(图即邻接点,树即子节点)。
经典例题一:二叉树的最小高度(leetcode.111)
怎么套到 BFS 的框架⾥呢?⾸先明确⼀下起点 start 和终点 target 是什么,怎么判断到达了终点?显然起点就是 root 根节点, 终点就是最靠近根节点的那个「叶⼦节点」嘛, 叶⼦节点就是两个⼦节点都是 null 的节点:
if(cur->left == nullprt && cur->right == nullptr)
//到达叶子结点,返回
那么,按照BFS框架稍加改造:
#include <queue>
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
int minDepth(TreeNode* root) {
if(root == nullptr)
return 0;
queue<TreeNode*> q;
// 将起点加入到队尾
q.push(root);
int depth = 1;
while(!q.empty()){
int sz = q.size();
// 将当前队列中的所有节点向四周扩散
for(int i = 0; i < sz; i++){
// 每次取出队首节点
TreeNode* cur = q.front();
q.pop();
// 这里判断是否到达终点
if(cur->left == nullptr && cur->right == nullptr)
return depth;
// 将cur的相邻节点加入队尾
if(cur->left != nullptr)
q.push(cur->left);
if(cur->right != nullptr)
q.push(cur->right);
}
// 划重点,更新步数在这里
depth++;
}
return depth;
}
};
经典例题二:打开转盘锁(leetcode.752)
直接上代码:
#include <string>
#include <queue>
class Solution {
public:
// 将s[i]向上拨
string plusOne(string s, int i){
if(s[i] =='9')
s[i] = '0';
else
s[i] = s[i] + 1;
return s;
}
// 将s[i]向下拨
string minusOne(string s, int i){
if(s[i] == '0')
s[i] = '9';
else
s[i] = s[i] - 1;
return s;
}
int BFS(vector<string> &deadends, string target){
queue<string> q;
set<string> visited;
set<string> deads;
for(int i = 0; i < deadends.size(); i++)
deads.insert(deadends[i]);
int step = 0;
q.push("0000");
visited.insert("0000");
while(!q.empty()){
int sz = q.size();
for(int i = 0; i < sz; i++){
string cur = q.front();
q.pop();
// 跳过死锁
if(deads.find(cur) != deads.end())
continue;
if(cur == target){
printf("%d
", step);
return step;
}
// 将该结点的所有子节点加入队尾
for(int i = 0; i < 4; i++){
string adjPlus = plusOne(cur, i);
string adjMinus = minusOne(cur, i);
if(visited.find(adjPlus) == visited.end()){
q.push(adjPlus);
visited.insert(adjPlus);
}
if(visited.find(adjMinus) == visited.end()){
q.push(adjMinus);
visited.insert(adjMinus);
}
}
}
step++;
}
return -1;
}
int openLock(vector<string>& deadends, string target) {
return BFS(deadends, target);
}
};