💓前言
BFS和DFS一样了,都是搜索界中独占一片天下的地位,但是可能有小伙伴感觉捋不清其执行的逻辑,本文主要是结合两种常见的刷题模式来演示怎么系统的拿捏住常规的宽搜的算法模板。
💓知识铺垫
多的不想放太多,感觉BFS和DFS一样,知识点了,就这么一点,但是它的题,真的挺多的,与其看概念,不如直接去磕题了,浅放一张英雄哥的知识总结吧。
今天的题是BFS,但是今天的每日一题要探索的点比较多,有12个,先用另外一个简单一点的BFS来熟悉一下,再解决今天的题吧。
💓例一、 P1443 马的遍历
BFS模板
💒题目描述
洛谷传送门
🌟解题报告
确实是传统的bfs的实现流程。相比于常规的,只是说,从四个方向变成了八个方向。然后在我现在做的题里,除了迷宫那个题,在确定偏移量数组的时候需要依据方向,其他情况下了,其实都可以,我现在默认的顺时针安排也是没有问题的,草稿纸上画的时候,不要画得太凌乱,不然不好数。 想记录的第二个点了,就是坐标点存储可以能用结构体直接就用结构体吧,不必搞得很妖艳,能Ac就是最好的。 bfs的题都有一个特点吧,姑且来说,就是到达某个点,最少要多少步,可达性的最短路了。 bfs的流程: 1、将当前传入bfs的坐标标记为使用过了,放入队列中 2、当队列不空的时候进入循环 3、取出当前队列的队头,待会拓展它 4、去除这个使用过的队头 5、结合偏移量数组枚举现在有点方向 6、遇到不合法的就跳过 7、将合法的,没有探索过的点加入队列,标记使用过了
🌻参考代码(C++版本)
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 410;
int n,m,x,y;
int dx[] = {1,2,2,1,-1,-2,-2,-1};
int dy[] = {2,1,-1,-2,-2,-1,1,2};
bool st[N][N];
int dist[N][N];
typedef pair<int, int> PII;
void bfs(int x,int y)
{
queue<PII> q;
q.push({x,y});
st[x][y] = true;
dist[x][y] = 0;
while(q.size())
{
auto hh = q.front();
q.pop();
for(int i = 0; i < 8;i++)
{
int xx = hh.first + dx[i];
int yy = hh.second + dy[i];
if(xx < 1 || xx > n || yy < 1 || yy > m) continue;
if(st[xx][yy] == false)
{
q.push({xx,yy});
st[xx][yy] = true;
dist[xx][yy] = dist[hh.first][hh.second] + 1;
}
}
}
}
int main()
{
cin >> n >> m >> x >> y;
memset(dist,-1,sizeof dist);
bfs(x,y);
for(int i = 1; i <= n;i++)
{
for(int j = 1; j <= m;j++)
printf("%-5d",dist[i][j]);
printf("\n");
}
return 0;
}
💓例二、 P1747 好奇怪的游戏
💒题目描述
原题传送门
🌟解题报告
其实玩法和上面的马的遍历差不多,只是说,当探索到(1,1)这个点之后,就要记录答案,结束搜索了。
然后搜索的流程就是上面第一题中我用红色字体备注出来的部分,多敲几遍其实就可能背得下来了。
想浅记一下需要注意的点: 一般取出队头以后,它是一个结构体或者是一个pair ,存储在其中的坐标(x,y)是需要通过.运算符 来获取的,切忌直接夸夸夸用x和y去更新新的点,编译不会报错,因为整个代码中确实有x和y,但是因为逻辑是错的,所以大概率是没有结果的
🌻参考代码(C++版本)
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 30;
int a1,b1,a2,b2,ans;
int dx[] = {1,2,2,2,2,1,-1,-2,-2,-2,-2,-1};
int dy[] = {2,2,1,-1,-2,-2,-2,-2,-1,1,2,2};
int dist[N][N];
bool st[N][N];
struct node
{
int x,y;
};
void bfs(int x,int y)
{
memset(dist,-1,sizeof dist);
memset(st,0,sizeof st);
queue<node> q;
q.push({x,y});
st[x][y] = true;
dist[x][y] = 0;
while(q.size())
{
auto hh = q.front();
q.pop();
if(hh.x == 1 && hh.y == 1)
{
ans = dist[hh.x][hh.y];
return ;
}
for(int i = 0; i < 12;i++)
{
int xx = hh.x + dx[i];
int yy = hh.y + dy[i];
if(xx > 0 && xx <= 20 && yy > 0 && yy <= 20 && !st[xx][yy])
{
st[xx][yy] = true;
dist[xx][yy] = dist[hh.x][hh.y] + 1;
q.push({xx,yy});
}
}
}
}
int main()
{
cin >> a1 >> b1 >> a2 >>b2;
bfs(a1,b1);
cout << ans << endl;
ans = 0;
bfs(a2,b2);
cout << ans << endl;
return 0;
}
💓例三、 LCP 44. 开幕式焰火
💒题目描述
原题传送门
🌟解题报告
遍历树(本质也就是结构体+指针)+使用哈希表的思想统计个数。可以使用BFS搜吧,但是我想放过自己,能Ac就好。
🌻参考代码(C++版本)
class Solution {
public:
vector<int> cnt = vector<int>(1010,0);
void pre_dfs(TreeNode* root)
{
if(root)
{
cnt[root->val]++;
if(root->left) pre_dfs(root->left);
if(root->right) pre_dfs(root->right);
}
return ;
}
int numColor(TreeNode* root) {
pre_dfs(root);
int ans = 0;
for(int i = 0; i < cnt.size();i++)
if(cnt[i]) ans++;
return ans;
}
};
💓例四、 102. 二叉树的层序遍历
💒题目描述
原题传送门
🌟解题报告
层序遍历本来也就是结合BFS进行的吧。层序遍历是先处理结根点,同时了,倘若有左右孩子,就将左右孩子入队,因此能够从上而下,逐层打印。按照这种先处理当前层的根节点,再处理下一层的叶子结点,也就是BFS的一层一层搜索的思想不谋而合了。
那就直接砸宽搜的板子吧,先将最开始的根节点root 入队,在队列不空的情况下进行取队头、拓展队头、更新入队信息的操作。
🌻参考代码(C++版本)
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> ans;
queue<TreeNode*> q;
if(root != nullptr) q.push(root);
while(q.size())
{
int n = q.size();
vector<int> tmp;
for(int i = 0; i < n;i++)
{
auto hh = q.front();
q.pop();
tmp.push_back(hh->val);
if(hh->left) q.push(hh->left);
if(hh->right) q.push(hh->right);
}
ans.push_back(tmp);
}
return ans;
}
};
💓例五、 1609. 奇偶树
💒题目描述
原题传送门
🌟解题报告
和上面一个题一样,看到层序遍历,其实就向着BFS的方向思考就好,和上面一个题换汤不换药了,按照题目意思模拟出来就好。 唯一需要注意的了,因为有升序、降序的要求,所以需要存储前驱结点的数据,但是倘若现在的处理根roo = 1 的左子树 = 10d 的情况,也就是它前面看起来是没有前驱结点的,所以这就需要我们初始化前驱结点。比如对于要递增的,就初始化为一个小于数据范围的数,比如我这里的-1 ,假如是递降的,就初始化一个大于数据范围的数,比如我这里的1e6+10 int pre_val = (level%2 == 0) ? -1 : 1e6 + 10;
🌻参考代码(C++版本)
class Solution {
public:
bool isEvenOddTree(TreeNode* root) {
queue<TreeNode*> q;
if(root != nullptr) q.push(root);
int level = 0;
while(!q.empty())
{
int n = q.size();
int pre_val = (level%2 == 0) ? -1 : 1e6 + 10;
for(int i = 0;i < n;i++)
{
auto hh = q.front();
q.pop();
int now_val = hh->val;
if(level % 2 == 0 && (now_val)%2 == 0 || level % 2 && now_val%2) return false;
if((level % 2) == 0 && now_val <= pre_val || (level % 2) && now_val >= pre_val) return false;
pre_val = now_val;
if(hh->left) q.push(hh->left);
if(hh->right) q.push(hh->right);
}
level ++;
}
return true;
}
};
还有一个推箱子的题,感觉暂时拿捏不住,不想磕它了,1263. 推箱子。等后面找个机会二刷吧 原题传送门
💓 总结
① 记住常规情况下的实现逻辑和算法板子 算法实现的伪代码大致可以这种描述 具体来说了,我现在遇到的,需要加偏移量来探索4个、8个、或者12个方向的情况比较比较多,原原本本使用邻接表的倒是少了。 ② 注意细节 在整个代码中,尽量不要出现重复名字的变量吧,有时候容易出现编译没有问题,但是逻辑是有问题的。然后注意,倘若是坐标点,一般是使用pair或者结构体存储,是需要具体的调用其中的数据的,不能直接拿着就用了。 ③ 层序遍历和宽搜了,基本是一个思想了,遇到层序遍历的题,可以直接使用宽搜模拟了
|