从ACM模式刷到核心代码模式,只为把宽搜彻底理解透彻

简介: 从ACM模式刷到核心代码模式,只为把宽搜彻底理解透彻

知识铺垫


多的不想放太多,感觉BFS和DFS一样,知识点了,就这么一点,但是它的题,真的挺多的,与其看概念,不如直接去磕题了,浅放一张英雄哥的知识总结吧。


今天的题是BFS,但是今天的每日一题要探索的点比较多,有12个,先用另外一个简单一点的BFS来熟悉一下,再解决今天的题吧。


例一、 P1443 马的遍历


BFS模板


题目描述


image.png

解题报告


确实是传统的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];//记录(x,y)到各个点之间的距离
typedef pair<int, int> PII;
//用结构体也是可以的
// struct node{
//  int x,y;
// };
void bfs(int x,int y)
{
  queue<PII> q;
  // queue<node> q;
  q.push({x,y});
  //标记(x,y)这个点已经走过了
  st[x][y] = true;
  //更新(x,y)到达点(x,y)的距离
  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);
  //遍历经过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 好奇怪的游戏


题目描述


image.png解题报告


其实玩法和上面的马的遍历差不多,只是说,当探索到(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();
    //到达(1,1)
    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. 开幕式焰火


题目描述


微信图片_20221019220948.png

解题报告


遍历树(本质也就是结构体+指针)+使用哈希表的思想统计个数。可以使用BFS搜吧,但是我想放过自己,能Ac就好。


参考代码(C++版本)

/**
 * 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:
    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. 二叉树的层序遍历


题目描述


微信图片_20221019221249.png


解题报告


层序遍历本来也就是结合BFS进行的吧。层序遍历是先处理结根点,同时了,倘若有左右孩子,就将左右孩子入队,因此能够从上而下,逐层打印。按照这种先处理当前层的根节点,再处理下一层的叶子结点,也就是BFS的一层一层搜索的思想不谋而合了。


那就直接砸宽搜的板子吧,先将最开始的根节点root入队,在队列不空的情况下进行取队头、拓展队头、更新入队信息的操作。


参考代码(C++版本)

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
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. 奇偶树


题目描述


微信图片_20221019221409.png

解题报告


和上面一个题一样,看到层序遍历,其实就向着BFS的方向思考就好,和上面一个题换汤不换药了,按照题目意思模拟出来就好。

唯一需要注意的了,因为有升序、降序的要求,所以需要存储前驱结点的数据,但是倘若现在的处理根roo = 1的左子树 = 10d的情况,也就是它前面看起来是没有前驱结点的,所以这就需要我们初始化前驱结点。比如对于要递增的,就初始化为一个小于数据范围的数,比如我这里的-1,假如是递降的,就初始化一个大于数据范围的数,比如我这里的1e6+10

int pre_val = (level%2 == 0) ? -1 : 1e6 + 10;


参考代码(C++版本)

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
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();
            //初始化待会用于比较的前驱值
            //倘若是再偶数下标层,初始化为-1
            //倘若是在奇数下标层,初始化10^6+10
            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;
    }
};

总结


① 记住常规情况下的实现逻辑和算法板子

微信图片_20221019221530.png

算法实现的伪代码大致可以这种描述

微信图片_20221019221547.png

具体来说了,我现在遇到的,需要加偏移量来探索4个、8个、或者12个方向的情况比较比较多,原原本本使用邻接表的倒是少了。

② 注意细节

在整个代码中,尽量不要出现重复名字的变量吧,有时候容易出现编译没有问题,但是逻辑是有问题的。然后注意,倘若是坐标点,一般是使用pair或者结构体存储,是需要具体的调用其中的数据的,不能直接拿着就用了。

③ 层序遍历和宽搜了,基本是一个思想了,遇到层序遍历的题,可以直接使用宽搜模拟了


相关文章
|
7月前
|
存储 安全 编译器
[笔记]读书笔记 C++设计新思维《一》基于策略的类设计(下)
[笔记]读书笔记 C++设计新思维《一》基于策略的类设计(下)
|
7月前
|
存储 前端开发 JavaScript
潮玩宇宙大逃杀无聊猿卷轴模式系统开发详细规则丨步骤需求丨方案项目丨技术架构丨源码功能
确定游戏类型和规则:明确无聊猿卷轴模式游戏类型和游戏规则,包括敌人类型、地图设计、任务类型、战斗机制等。
|
6天前
|
存储 Web App开发 运维
发布、部署,傻傻分不清楚?从概念到实际场景,再到工具应用,一篇文章让你彻底搞清楚
部署和发布是软件工程中经常互换使用的两个术语,甚至感觉是等价的。然而,它们是不同的! • 部署是将软件从一个受控环境转移到另一个受控环境,它的目的是将软件从开发状态转化为生产状态,使得软件可以为用户提供服务。 • 发布是将软件推向用户的过程,应用程序需要多次更新、安全补丁和代码更改,跨平台和环境部署需要对版本进行适当的管理,有一定的计划性和管控因素。
180 1
|
6天前
|
存储 算法 Java
【底层服务/编程功底系列】「手把手教学系列」带你打造一个属于自己的规则引擎服务,打破任何业务难题(逻辑模型和API设计)(一)
【底层服务/编程功底系列】「手把手教学系列」带你打造一个属于自己的规则引擎服务,打破任何业务难题(逻辑模型和API设计)
35 1
|
6天前
|
存储 设计模式 监控
【底层服务/编程功底系列】「手把手教学系列」带你打造一个属于自己的规则引擎服务,打破任何业务难题(逻辑模型和API设计)(二)
【底层服务/编程功底系列】「手把手教学系列」带你打造一个属于自己的规则引擎服务,打破任何业务难题(逻辑模型和API设计)
32 0
|
6天前
|
Java API
【底层服务/编程功底系列】「手把手教学系列」带你打造一个属于自己的规则引擎服务,打破任何业务难题(逻辑模型和API设计)(三)
【底层服务/编程功底系列】「手把手教学系列」带你打造一个属于自己的规则引擎服务,打破任何业务难题(逻辑模型和API设计)
30 0
|
6天前
|
缓存 应用服务中间件 数据库
【分布式技术专题】「缓存解决方案」一文带领你好好认识一下企业级别的缓存技术解决方案的运作原理和开发实战(多级缓存设计分析)
【分布式技术专题】「缓存解决方案」一文带领你好好认识一下企业级别的缓存技术解决方案的运作原理和开发实战(多级缓存设计分析)
53 1
|
6天前
|
存储 缓存 监控
【分布式技术专题】「缓存解决方案」一文带领你好好认识一下企业级别的缓存技术解决方案的运作原理和开发实战(数据更新场景策略和方案分析)
【分布式技术专题】「缓存解决方案」一文带领你好好认识一下企业级别的缓存技术解决方案的运作原理和开发实战(数据更新场景策略和方案分析)
16 0
|
6月前
|
运维 前端开发 安全
万字长文搞懂产品模式和项目模式
万字长文搞懂产品模式和项目模式
74 0
|
7月前
|
安全 Java C++
[笔记]读书笔记 C++设计新思维《一》基于策略的类设计(上)
[笔记]读书笔记 C++设计新思维《一》基于策略的类设计