当前位置: 首页 > article >正文

算法学习(19)—— 队列与 BFS

关于bfs

bfs又称宽搜,全称是“宽度优先遍历”,然后就是关于bfs的三个说法:“宽度优先搜索”,“宽度优先遍历”,“层序遍历”,这三个都是同一个东西,前面我们介绍了大量的深度优先遍历的题目已经衍生算法,对这类比较熟悉的

在前面讲解深搜之前,我们上一篇数据结构相关的文章是介绍栈的,也列举了一些与栈相关的题目:算法学习(十一)—— 栈-CSDN博客

纯队列的题目很少很少,队列这种数据结构大部分都是用来服务BFS算法的,所以我们就把BFS和队列放一起来介绍。

和深搜一样,宽搜不仅仅只应用在树形结构中,这篇文章只列举了部分在树中的应用,先熟悉下BFS算法,后面会再展开细讲 

部分OJ题详解

429. N叉树的层序遍历

429. N 叉树的层序遍历 - 力扣(LeetCode)

第一道题就是简单的开胃菜,题目很简单,就是按顺序返回每一层的节点,下面来分析下这道题:

  • 绝大部分的bfs题的代码类系都差不多,相当于有一个模板。因为从左往右遍历第一层后,要再次从最左边的节点继续往下遍历,所以我们是需要借助队列这个数据结构的,因为队列是先进先出的,遍历最左边节点时需要记录下,以便往下一层遍历时能找到最左边节点
  • 这第一题我们就详细一点介绍下层序遍历的步骤:
  • ①先搞一个队列,然后如果根节点不为空,让根节点入队列
  • ②然后就是一个循环,条件是队列不为空时,先拿出队头元素,把队头元素的所有孩子节点都入队列,然后pop掉队头元素,一直重复这步,直到队列为空,层序遍历完成‘
  • ③然后就是统计元素的时机,每次遍历时,先统计下队列里有多少元素,因为此时队列里元素的个数刚好是这一层元素的个数,然后按照数量执行步骤②即可
class Solution 
{
public:
    queue<Node*> q;
    vector<vector<int>> ret;
    vector<vector<int>> levelOrder(Node* root) 
    {
        bfs(root);
        return ret;
    }
    void bfs(Node* root)
    {
        if(root == nullptr) return;
        q.push(root); //先把根节点入队列
        while(!q.empty())
        {
            int size = q.size(); //记录此时队列元素个数,该个数代表本层节点的个数
            vector<int> v;
            for(size_t i = 0; i < size; i++)
            {
                Node* cur = q.front(); //拿到队头元素
                q.pop();
                v.push_back(cur->val);
                for(auto e : cur->children) //依次把队头下一层节点入队列
                {
                    q.push(e);
                }
            }
            ret.push_back(v);
        }
    }
};

103. 二叉树的锯齿形层序遍历

103. 二叉树的锯齿形层序遍历 - 力扣(LeetCode)

锯齿形遍历,就是先从左往右(包括根节点),再从右往左遍历,依次循环直到遍历完成,其它的和第一题是一样的:

  • 只需要在上一道题的基础上稍加修改即可;只需要把偶数行的结果在添加进最终数组之前,逆序一下即可
class Solution 
{
    queue<TreeNode*> q;
    vector<vector<int>> ret;
    bool e = true;
public:
    vector<vector<int>> zigzagLevelOrder(TreeNode* root) 
    {
        bfs(root);
        return ret;
    }
    
    void bfs(TreeNode* root)
    {
        if(root == nullptr) return;
        q.push(root); //先把根节点入队列
        while(!q.empty())
        {
            int size = q.size();
            vector<int> v;
            for(size_t i = 0; i < size; i++)
            {
                TreeNode* cur = q.front(); //拿到队头元素
                q.pop();
                v.push_back(cur->val);
                if(cur->left) q.push(cur->left);
                if(cur->right) q.push(cur->right);
            }
            if(e == true)
            {
                ret.push_back(v);
                e = false;
            }
            else
            {
                reverse(v.begin(), v.end());
                ret.push_back(v);
                e = true;
            }
        }
    }
};

662. 二叉树的最大宽度

662. 二叉树最大宽度 - 力扣(LeetCode)

宽度定义为“最左节点和最右节点之间的距离”,在这道题中,将题目给的二叉树视作满二叉树,两端点中间会出现一些延申到这一层的空节点,并且这些空节点也计入长度,下面来分析下这道题:

解法一: 

  • 解法一:硬来;由于空节点也要计入长度,如果遇到空节点,把空节点也加入到队列里,然后后面遇到空节点时,默认加两个空节点即可
  • 但是这样搞会有问题,以示例二为例,由于9只有左孩子一个孩子,那么右孩子会当成空加入到队列,但是这样的话队列的长度就是8了,不符合要求;这个也可以解决,就是用一个变量empty,统计空节点的个数,比如从6开始,empty一直++,直到遇到不是空节点也就是7时,empty + 2就是我们需要的长度。这种解法理论可行,但是提交过后会超时,假设3000个节点,然后只有2两条路径,就是纯左边和右边一路干到底,那么这个时间复杂度就非常夸张了,如下图:

解法二

  • 解法二:利用数组存储二叉树的方式给节点编号;树是可以通过顺序的方式存储的,比如堆结构,就是用的数组形式
  • 我们先回一下用数组存储二叉树:默认根节点的下标是从1开始计数,然后按照层次遍历的顺序依次遍历并且入数组。所以就可以发现一个规律,对于每个节点都有:“假设一个节点下标是x,那么它的左孩子下标是2x,右孩子下标是2x + 1”:
  • 所以我们仍然创建一个队列,但是队列不再存int了,而是存一个键值对pair,first表示这个节点的指针,second表示这个节点的编号,然后对于左右孩子的下标就按照上面的公式入队列
  • 然后就是计算宽度,直接拿出队头和队尾的元素,然后让两个的下标相减再+1就是我们最终需要的值
  • 细节一:如果再优化的话,我们可以直接用数组vector<pair<TreeNode*, int>>来代替队列,因为对于不同的语言对于队列的实现是不一样的,有的队列的容器可以查队头队尾,有的只能查队尾
  • 细节二:下标又可能溢出,以解法一的极端情况为例,左右如果各有1500个节点,那么数量就是2的1500次方-1,这个数是非常大的,任何数据类型都存不下;但是当我们相减之后,即使结果溢出,结果也是正确的,因为数据的存储可以看作是一个环形的,比如char类型,如下图:
  • 整型也是同理,就算溢出,相减完之后结果依旧是正确的,但是C++中用int的话会直接报错,所以我们用无符号整数unsigned int来存下标就OK了;如果没想到用unsigned int,那么就可以用字符串来存这个数,然后最后减法的时候做一次高精度减法也是可以的

 

class Solution 
{
    vector<pair<TreeNode*, unsigned int>> v;
    unsigned int ret;
public:
    int widthOfBinaryTree(TreeNode* root) 
    {
        bfs(root);
        return ret;
    }
    void bfs(TreeNode* root)
    {
        v.push_back({root, 1}); //先把根节点放进去
        while(!v.empty())
        {
            auto& [x1, y1] = v[0]; //拿到首元素
            auto& [x2, y2] = v.back(); //拿到结尾元素
            ret = max(ret, y2 - y1 + 1);

            //遍历下一层
            vector<pair<TreeNode*, unsigned int>> tmp; //让下一层进入这个队列,然后覆盖掉前面那个即可
            for(auto& [x, y] : v)
            {
                if(x->left) tmp.push_back({x->left, y * 2});
                if(x->right) tmp.push_back({x->right, y * 2 + 1});
            }
            v = tmp; //覆盖
        }
    }
};

515. 在每个树行中找最大值

515. 在每个树行中找最大值 - 力扣(LeetCode)

题目很简单,就算让我们找出一棵树中每一行的最大值然后返回即可:

  • 算法也很简单,就是在层序遍历的过程中,用一个变量记录下最大值,然后在进入下一次遍历之前记录一下最大值即可
class Solution 
{
    queue<TreeNode*> q;
    vector<int> ret;
public:
    vector<int> largestValues(TreeNode* root) 
    {
        bfs(root);
        return ret;
    }
    void bfs(TreeNode* root)
    {
        if(root == nullptr) return;
        q.push(root);
        while(!q.empty())
        {
            int tmp = INT_MIN;
            int sz = q.size();
            for(int i = 0; i < sz; i++)
            {
                auto t = q.front();
                q.pop();
                tmp = max(tmp, t->val);
                if(t->left) q.push(t->left);
                if(t->right) q.push(t->right);
            }
            ret.push_back(tmp);
        }
    }
};

http://www.kler.cn/a/457966.html

相关文章:

  • 安卓系统主板_迷你安卓主板定制开发_联发科MTK安卓主板方案
  • 人工智能(AI)简史:推动新时代的科技力量
  • uniapp3 手写签名组件(vue3 语法)封装与应用
  • JVM对象内存分配
  • vim里搜索关键字
  • github提交不上去,网络超时问题解决
  • 短视频矩阵系统后端源码搭建实战与技术详解,支持OEM
  • Move AI技术浅析(五):动作识别与分类
  • JMeter 如何并发执行 Python 脚本
  • 14. HDFS基准测试
  • C++软件设计模式之命令模式
  • DataWhale之工作流
  • 基于Golang的记账系统的服务端的设计与实现
  • sentinel-请求限流、线程隔离、本地回调、熔断
  • 算法题(18):删除有序数组中的重复项2
  • 数据的简单处理——pandas模块——选择数据
  • Linux系统离线部署MySQL详细教程(带每步骤图文教程)
  • 常见的排序算法过程和比较分析
  • NLP基础知识 - 向量化
  • 人工智能-Python网络编程-HTTP
  • MNER多模态实体识别论文介绍,有关大模型和chatgpt
  • 《机器学习》从入门到实战——线性回归
  • 使用seata实现分布式事务管理
  • 【畅购商城】购物车模块之修改购物车以及结算
  • 【总结整理】 神经网络与深度学习 邱锡鹏 课后习题答案 扩展阅读链接
  • L2TP_VPN和IPSec_VPN