【LeetCode Cookbook(C++ 描述)】一刷二叉树之层序遍历(BFS)

news2024/11/26 12:16:05

目录

  • LeetCode #102:Binary Tree Lever Order Traversal 二叉树的层序遍历
    • 递归解法
    • 迭代解法
  • LeetCode #107:Binary Tree Level Order Traversal II - 二叉树的层序遍历 II
    • 递归解法
    • 迭代解法
  • LeetCode #429:N-ary Tree Level Order Traversal - N 叉树的层序遍历
  • LeetCode #637:Average of Levels in Binary Tree 二叉树的层平均值
    • 广度优先搜索(BFS)
    • 深度优先搜索(DFS)

本系列文章仅是 GitHub 大神 @halfrost 的刷题笔记 《LeetCode Cookbook》的提纲以及示例、题集的 C++转化。原书请自行下载学习。
本篇文章涉及新手应该优先刷的几道经典二叉树层序遍历算法题。

LeetCode #102:Binary Tree Lever Order Traversal 二叉树的层序遍历

#102
给你一个二叉树,请你返回其按层序遍历得到的节点值。

递归解法

递归法并不符合层序遍历的初衷。递归是基于深度的,通过“解决到底”(即达到基准情况)将问题分解成规模更小的相同问题,直到问题小到可以直接解决(这被称为基准情况或递归终止条件),然后将这些小规模问题的解合并起来,即逐步回溯,形成原问题的解,而层序遍历需要一层一层地遍历,这显然不太符合广度优先搜索(BFS)的基本思想。

但是,我们依然可以利用递归实现二叉树的层序遍历。层序遍历是每一层的节点从左到右的遍历,在遍历的时候我们可以先遍历左子树,再遍历右子树。假设我们有如图的一颗二叉树:

在这里插入图片描述

遍历的顺序应为:3 -> 9 -> 3 -> 20 -> 15 -> 20 -> 7

因此,在遍历左子树或者右子树的时候,涉及到向上或者向下遍历,为了让递归的过程中的同
一层的节点放在同一个列表中,在递归时要记录深度 depth ;同时,每次遍历到一个新的 depth ,结果数组中没有对应 depth 的列表时,则在结果数组中创建一个新的列表保存该 depth 的节点。

//若当前行对应的列表不存在,则添加一个空列表
if (res.size() < depth) res.push_back(vector<int>());

我们利用递归的性质,每次向下深入均记录一次深度,并将相应节点值存储在对应深度的数组中,最终返回一个二维数组。

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>> res;
        if (root == nullptr) return res;
        level(root, 1, res);
        
        return res;
    }

private:
    void level(TreeNode* root, int depth, vector<vector<int>>& res) {
        if (root == nullptr) return;  // base case
        //若当前行对应的列表不存在,则添加一个空列表
        if (res.size() < depth) res.push_back(vector<int>());
        //将当前节点的值添加到当前深度的列表中
        res[depth - 1].push_back(root->val);
        //递归处理左子树
        if (root->left) level(root->left, depth + 1, res);
        //递归处理右子树
        if (root->right) level(root->right, depth + 1, res);
    }
};

该算法的时间复杂度为   O ( n ) \ O(n)  O(n) ,由于维护了一个结果数组 res ,空间复杂度为   O ( n ) \ O(n)  O(n)

需要注意的是,尽管这一解法使用了递归,但它在这里更多地是作为一种遍历树的手段,通过深度来控制每一层数据的收集。这种方法的优点是代码简洁,但不是处理大型二叉树时最高效的方法,过深的递归深度可能会导致栈溢出。对于大型树,迭代法更合适

迭代解法

我们利用队列保存每一层的所有节点,把队列里的所有节点弹出队列,然后把这些节点各自的子节点入队列,以此来完成对每一层的遍历。

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>> res;
        if (root == nullptr) return res;

        queue<TreeNode*> q;
        q.push(root);

        while (!q.empty()) {
            int levelSize = q.size();   //当前层的节点数
            vector<int> currentLevel;

            for (int i = 0; i < levelSize; ++i) {
                TreeNode* node = q.front();
                q.pop();
				//将当前节点的值加入当前层的结果中
                currentLevel.push_back(node->val); 
                //若节点存在左子树,入队
                if (node->left) q.push(node->left);
                //若节点存在右子树,入队
                if (node->right) q.push(node->right);
            }
			//将当前层的结果加入最终结果中
            res.push_back(currentLevel); 
        }

        return res;
    }
};

LeetCode #107:Binary Tree Level Order Traversal II - 二叉树的层序遍历 II

#107
给你二叉树的根节点 root ,返回其节点值自底向上的层序遍历(即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)。

与 #102 题(上一题)原理一致,按照正常层序遍历,获得结果后再将其逆序输出即可。

递归解法

我们依然是先遍历左子树,再遍历右子树,在递归时记录深度 depth ,不断更新每一层的 depth ,采用的 level() 方法与 #102 题一致。

class Solution {
public:
    vector<vector<int>> levelOrderBottom(TreeNode* root) {
        vector<vector<int>> res;
        level(root, 1, res);
        reverse(res.begin(), res.end());   //反转结果
        
        return res;
    }

private:
    void level(TreeNode* root, int depth, vector<vector<int>>& res) {
        if (root == nullptr) return;
        //若当前行对应的列表不存在,加一个空列表
        if (res.size() < depth) res.push_back(std::vector<int>());
        //将当前节点的值加入当前行的res中
        res[depth - 1].push_back(root->val);
        //递归处理左子树
        if (root->left) level(root->left, depth + 1, res);
        //递归处理右子树
        if (root->right) level(root->right, depth + 1, res);
    }
};

迭代解法

同 #102 题解法,最终只需要将结果逆序输出即可。

class Solution {
public:
    vector<vector<int>> levelOrderBottom(TreeNode* root) {
    	vector<vector<int>> res;
    	if (root == nullptr) return res;
    
	    queue<TreeNode*> q;
	    q.push(root);
	
	    while (!q.empty()) {
	        int levelSize = q.size();
	        vector<int> currentLevel;
	
	        for (int i = 0; i < levelSize; ++i) {
	            TreeNode* node = q.front();
	            q.pop();
	            currentLevel.push_back(node->val);
	
	            if (node->left) q.push(node->left);
	            if (node->right) q.push(node->right);

	        }
	
	        res.push_back(currentLevel);
	    }
	    //反转结果,从底部到顶部
	    reverse(res.begin(), res.end());
	
	    return res;
    }

};

LeetCode #429:N-ary Tree Level Order Traversal - N 叉树的层序遍历

#429
给定一个 N 叉树,返回其节点值的层序遍历。 树的序列化输入是用层序遍历,每组子节点都由 null 值分隔。

与处理二叉树类似,我们依然利用队列保存每一层的所有节点,把队列里的所有节点弹出队列,然后把这些节点各自的子节点入队列

class Solution {
public:
    vector<vector<int>> levelOrder(Node* root) {
        //如果根节点为空,则直接返回一个空的二维向量
        if (!root) return {};
        //存储层序遍历的结果
        vector<vector<int>> res;
        queue<Node*> q;
        //将根节点入队
        q.push(root);
        //当队列不为空时,进行循环遍历
        while (!q.empty()) {
            //获取当前层的节点数,即队列的大小
            int levelSize = q.size();
            //存储当前层的所有节点值
            vector<int> currentLevel;
            //遍历当前层的所有节点
            for (int i = 0; i < levelSize; ++i) {
                //从队列中取出当前节点
                Node* cur = q.front();
                q.pop();
                //将当前节点的值添加到 currentLevel 向量中
                currentLevel.push_back(cur->val);
                //遍历当前节点的所有子节点,并将它们入队
                for (Node* child : cur->children) q.push(child);
            }
            //将当前层的节点值向量添加到 res 中
            res.push_back(move(currentLevel));   //注意这里使用了 move(),以优化性能
        }
        //返回层序遍历的结果
        return res;
    }
};

该算法的时间复杂度为   O ( n ) \ O(n)  O(n),空间复杂度为   O ( n ) \ O(n)  O(n)

LeetCode #637:Average of Levels in Binary Tree 二叉树的层平均值

#637
给定一个非空二叉树的根节点 root ,以数组的形式返回每一层节点的平均值

广度优先搜索(BFS)

从根节点开始搜索,每一轮遍历同一层的全部节点,记录该层的节点总值和节点数,求该层平均值加入 res 结果集中,直至完全遍历整棵二叉树。

借鉴层序遍历的做法,我们可以使用队列存储待访问节点,只要确保在每一轮遍历时,队列中的节点是同一层的全部节点即可

首先,初始化队列和结果集,将根节点入队列:

vector<double> res;
queue<TreeNode*> q;
q.push(root);

每一轮遍历时,将队列中的节点全部弹出,记录这些节点的数量以及它们的节点值之和,并计算这些节点的平均值,然后将这些节点的全部非空子节点加入队列,重复上述操作直到队列为空,遍历结束;同时,我们在每一轮遍历之前获得队列中的节点数量 levelSize ,遍历时只遍历 levelSize 个节点,即可满足每一轮遍历的是同一层的全部节点。

class Solution {
public:
    vector<double> averageOfLevels(TreeNode* root) {
        vector<double> res;
        queue<TreeNode*> q;
        q.push(root);
        
        while (!q.empty()) {
            double sum = 0;
            int levelSize = q.size();
            for (int i = 0; i < levelSize; i++) {
                TreeNode* node = q.front();
                q.pop();
                //对每层节点求和
                sum += node->val;
                //加入非空子节点
                TreeNode* left = node->left, *right = node->right;
                //为避免定义变量类型失误,可以考虑如下写法:
                // auto left = node->left, right = node->right;
                if (left != nullptr) q.push(left);
                if (right != nullptr) q.push(right);
            }
            //计算每层节点值的平均值
            res.push_back(sum / levelSize);
        }
        
        return res;
    }
};

该算法的时间复杂度为   O ( n ) \ O(n)  O(n) ,需要维护一个队列,空间复杂度为   O ( n ) \ O(n)  O(n)

深度优先搜索(DFS)

我们维护两个数组,count 用于存储二叉树的每一层的节点数,sum 用于存储二叉树的每一层的节点值之和。搜索过程中记录递归深度 depth ,检查当前层数depth 是否已经存在于 sumcount 中,如果访问到的节点在第 i 层,则count[i] 的值自增,并将该节点的值添加到 sum[i] ;如果当前层数大于 sum.size() ,说明进入了新的层,就在 sumcount 的末端添加新的元素,分别初始化当前节点的值为 1 。

遍历结束之后,i 层的平均值即为 sum[i] / count[i]

class Solution {
public:
    vector<double> averageOfLevels(TreeNode* root) {
        vector<int> count;
        vector<double> sum;
        //遍历树,并更新每一层的节点数量以及值的总和
        dfs(root, 0, count, sum);
        //存储每一层的平均值
        vector<double> res;
        //遍历 sum 和 count 向量,计算每一层的平均值并添加到 res 中
        int size = sum.size();
        for (int i = 0; i < size; i++) res.push_back(sum[i] / count[i]);

        return res;
    }

private:
    void dfs(TreeNode* root, int depth, vector<int> &count, vector<double> &sum) {
        // base case
        if (root == nullptr) return;
        //如果当前层数小于 sum 和 count 的大小,则更新该层的节点值总和以及节点数量
        if (depth < sum.size()) {
            sum[depth] += root->val;
            count[depth]++;
        } else {
            //如果当前层数大于 sum 和 count 的大小,说明遇到了新的层,需要添加新的元素
            sum.push_back(1.0 * root->val);   //注意这里将值转换为 double 类型
            count.push_back(1);
        }
        //递归地对左子树进行深度优先搜索,层数加 1
        dfs(root->left, depth + 1, count, sum);
        //递归地对右子树进行深度优先搜索,层数也加 1
        dfs(root->right, depth + 1, count, sum);
    }
};

呜啊?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2035484.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

8月13日

思维导图 作业 TCP机械臂测试 通过w(红色臂角度增大)s(红色臂角度减小)d(蓝色臂角度增大)a(蓝色臂角度减小)按键控制机械臂 代码 #include<myhead.h>#define SER_PORT 8888 #define SER_IP "192.168.0.108" #define CLI_PORT 6666 #define CLI_IP "192.…

Unity数据持久化 之 LitJson序列化与反序列化

语法规则可以看这篇文章&#xff1a;Unity数据持久化 之 Json 语法速通-CSDN博客 1.LitJson是什么 LitJSON - Home&#xff0c;Release LitJSON 0.19.0 LitJSON/litjson GitHub LitJSON是一个net库&#xff0c;用于处理与JSON (JavaScript Object Notation)字符串之间的转换…

【RTOS面试题】临时屏蔽/禁用中断的方法有什么用?什么时候用?做这种方法时应该注意什么?

目录 一、临时屏蔽中断的用途二、使用场景三 、 注意事项四、 示例代码五、结论 临时屏蔽/禁用中断的方法在嵌入式系统开发中非常重要&#xff0c;尤其在处理中断密集型的任务时。下面将详细介绍这种方法的用途、应用场景以及注意事项。 一、临时屏蔽中断的用途 保护关键代码段…

GStreamer 简明教程(二):基本概念介绍,Element 和 Pipeline

系列文章目录 GStreamer 简明教程&#xff08;一&#xff09;&#xff1a;环境搭建&#xff0c;运行 Basic Tutorial 1 Hello world! 文章目录 系列文章目录前言一、查看插件信息1.1 gst-inspect 介绍1.2 源码中运行 gst-inspect1.3 理解插件的基本信息1.4 插件与元素1.5 总结…

Stable Diffusion XL【模型推荐】沙雕手绘Lora,贼开心!不要问我这个有什么用,因为只有真正懂沙雕的才知道

前言 hello&#xff0c;大家好** 看惯了满屏的精致画面&#xff0c;咱们也来改改画风。今天老徐给大家带来了一款别有风趣的Lora模型——YFilter_ShaDiaoShouHui沙雕手绘模型。看腻了精致严谨的作品&#xff0c;这块模型肯定让你觉得太惊艳了。用作者的话说——不要问我这个Lo…

二叉树------最小堆,最大堆。

什么是最小堆&#xff1a; 堆是一种二叉树&#xff0c;最小堆中所有父亲节点的值都要比自己的子节点的值要小。而根节点称为堆顶。根据定义我们可以得到堆中最小元素就在堆顶。&#xff08;节点左上角是编号&#xff0c;内部是元素值&#xff09; 假设该图中的堆顶元素是24呢&a…

【Python】Python单元测试

文章目录 01-单元测试基础什么是单元测试常用的文件结构运行单元测试 02. 断言函数03. Test Fixtures什么是Test Fixtures模块级别的Fixtures类级别的Fixtures方法级别的Fixtures 04.Mock 01-单元测试基础 什么是单元测试常用的文件结构编写第一个单元测试运行单元测试 什么是单…

在CentOS 7 上安装和配置 uwsgi 详细教程

本章教程,主要记录在CentOS7中成功安装uwsgi的详细步骤。 1. 更新系统包 首先,更新系统的包管理器以确保你有最新的软件包信息: sudo yum update -y2. 安装Python和pip CentOS 7 默认提供 Python 2.7,但你可能需要安装 Python 3 及其对应的 pip。以下是安装 Python 3 和…

OpenCV—二值化Threshold()、adaptiveThreshold()

cv2.threshold() c&#xff1a;double cv::threshold ( InputArray src, OutputArray dst, double thresh, double maxval, int type ) (注&#xff1a;源图片, 目标图, 阈值, 填充色, 阈值类型) python:cv.threshold(src,thresh, maxval, type[, dst]) src&#xff1a;源图片…

顶顶通呼叫中心中间件-通话之前录音配置方法(mod_cti基于FreeSWITCH)

顶顶通呼叫中心中间件-通话之前录音配置方法(mod_cti基于FreeSWITCH) 1、修改配置文件 点击配置文件 -> 点击vars -> 根据图中配置 -> 点击提交XML ->重新启动freeswitch 修改成true就是电话接通开始录音&#xff0c;修改成false就是通话之前开始录音。 <!--应…

ES环境搭建、ES安装

文章目录 简介与环境搭建全文检索倒排索引ElasticSearchWindows安装ES下载配置JDK环境启动ES服务 centos7安装ES下载ElasticSearch创建es用户配置JDK环境配置ElasticSearch配置JVM参数启动ElasticSearch服务常见启动报错 客户端Kibana安装下载修改Kibana.yml运行Kibana访问 ES安…

IntelliJ IDEA全新版的0个新特性【送源码】

jetBrains刚刚发布了最新IntelliJ IDEA 2024.2版本&#xff0c;做了不少优化性能方面的优化&#xff0c;同时新的ui也默认为启动ui。感兴趣的小伙伴可以下载体验&#xff0c;以下为官方本次介绍&#xff1a; 借助 IntelliJ IDEA 2024.2 Ultimate&#xff0c;您可以直接在 IDE …

宝塔面板实现定时任务删除 logs文件 加条件删除 只删除一个月前的日志

我们在开发中难免用到了日志功能&#xff0c;随着日志越来越多导致占用我们的内存 下面是一个简单的 使用宝塔面板里面的定时任务来实现删除日志案例 第一步 首先我的日志文件目录 都在log文件夹里面&#xff0c; 每个月生成一个日志文件夹 文件夹命名是年月来命名的 第二…

JVM(一) 类加载器、类加载过程、JVM参数设置

JVM Java编译器 Java源文件在通过编译器之后被编译成相应的.Class文件&#xff08;字节码文件&#xff09; JVM解释器 .Class文件又被JVM中的解释器编译成机器码在不同的操作 系统&#xff08;Windows、Linux、Mac&#xff09;上运行。每种操作系统的解释器都是不同的&#xf…

SpringBoot起步依赖和配置

目录 起步依赖 目的 1 继承父工程&#xff0c;确定版本信息 2 添加 starter-web 坐标信息 配置 配置文件的分类 ​编辑实操 1 后缀名是.properties文件修改端口号&#xff0c;把原本的默认端口号8080改为8082 2 创建后缀名是.yaml文件&#xff0c;尝试修改端口号&…

ARM——体系结构

计算机体系结构&#xff1a;冯诺伊曼 哈佛 冯诺依曼结构 冯诺依曼结构&#xff0c;也称冯诺依曼模型或普林斯顿结构&#xff0c;是根据冯诺依曼提出的存储程序概念设计的计算机体系结构。其主要特点包括&#xff1a; 存储程序&#xff1a;指令与数据都…

【Qt】信号与槽(下)

目录 自定义信号 带参数的信号和槽 信号和槽存在的意义 信号与槽的连接方式 一对一 一对多 多对一 意义 信号和槽的其他说明 信号和槽的断开 使用Lambda表达式定义槽函数 信号与槽的优缺点 优点: 松散耦合 缺点: 效率较低 自定义信号 自定义槽函数是非常关键的&a…

VMware 安装 centos7.9教程

目录 VMware17安包装 1.创建新的虚拟机 2.稍后安装一个系统 3.选择Centos7 4.选择安装位置 5.指定磁盘内存大小 6.自定义硬件 7.编辑内存 8.编辑处理器 9.选择安装镜像位置 10.选择网络适配器 11.播放虚拟机 12.选择语言简体中文 13.选择时区时间 14.最小化安装 …

电机学习-基础知识

文章目录 1 基本物理概念1.1 左手定则1.2 安培定则1.3 感应电动势 2 电机简单分类2.1 直流有刷电机2.2 步进电机2.2.1 步进电机的驱动原理1.相与线2.极性3.步进电机的驱动 2.3 无刷电机2.3.1 充磁方式2.3.2正弦波电动势与梯型电动势 3 编码器3.1 霍尔编码器3.2 光电编码器3.3 增…

TexWorks配置使用latexmk实现增量编译

TexWorks配置使用latexmk实现增量编译 TexWorks默认使用pdfLaTeXMakeIndexBibTeX&#xff0c;修改后&#xff0c;重新编译等待时间较长。本文旨在说明配置TexWorks使用latexmk实现增量式编译&#xff0c;加速编译。 前置条件说明 (1) 通过miktex自动安装了TexWorks及自带pdf…