从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或者结构体存储,是需要具体的调用其中的数据的,不能直接拿着就用了。

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


相关文章
|
存储 安全 编译器
[笔记]读书笔记 C++设计新思维《一》基于策略的类设计(下)
[笔记]读书笔记 C++设计新思维《一》基于策略的类设计(下)
|
5月前
|
Windows
技术好文共享:简单介绍SXS的一些有意思的特性
技术好文共享:简单介绍SXS的一些有意思的特性
|
6月前
|
程序员 Python
类的设计奥秘:从代码到架构的科普全解
类的设计奥秘:从代码到架构的科普全解
95 2
|
安全 Java C++
[笔记]读书笔记 C++设计新思维《一》基于策略的类设计(上)
[笔记]读书笔记 C++设计新思维《一》基于策略的类设计
|
测试技术
【解决方案 二十一】系统专业名词梳理及释义
【解决方案 二十一】系统专业名词梳理及释义
121 0
|
算法 Go
阐述:one wiex壹维克斯平台逻辑系统开发项目模式方案
阐述:one wiex壹维克斯平台逻辑系统开发项目模式方案
415 0
|
移动开发 JSON 小程序
【小程序开篇】小程序架构和配置
【小程序开篇】小程序架构和配置
283 0
【小程序开篇】小程序架构和配置
|
程序员
[温故知新] 编程原则和模式
温故而知新,聊一聊现代编程几大常见的编程原则
|
5G 测试技术 调度
广覆盖需求的实现 | 带你读《5G 空口设计与实践进阶 》之三
覆盖是 NR 实现高速率、低时延、大连接等其他性能指标的基础。为满足连续广覆盖的需求,NR 在覆盖方面进行了全方位的增强设计。
广覆盖需求的实现 | 带你读《5G 空口设计与实践进阶 》之三
|
项目管理
带你读《软件项目管理案例教程(第4版)》之二:项目确立
本书以案例形式讲述软件项目管理过程,借助路线图讲述项目管理的理论、方法及技巧,覆盖项目管理十大知识域的相关内容,重点介绍软件这个特殊领域的项目管理。本书综合了多个学科领域,包括范围计划、成本计划、进度计划、质量计划、配置管理计划、风险计划、团队计划、干系人计划、沟通计划、合同计划等的制定,以及项目实施过程中如何对项目计划进行跟踪控制。该书取材新颖,注重理论与实际的结合,通过案例分析帮助读者消化和理解所学内容,既适合作为高等院校计算机、软件及相关专业高年级本科生和研究生的教材,也适合作为广大软件技术人员和项目经理培训的教材,还可作为软件开发项目管理人员的参考书。