【每日算法】

算法第15天| (二叉树part02)层序遍历、226.翻转二叉树(优先掌握递归)、101. 对称二叉树(优先掌握递归)

文章目录

  • 算法第15天| (二叉树part02)层序遍历、226.翻转二叉树(优先掌握递归)、101. 对称二叉树(优先掌握递归)
  • 一、层序遍历
  • 二、226. 翻转二叉树(优先掌握递归)
  • 三、101. 对称二叉树(优先掌握递归)


二叉树理论基础

一、层序遍历

代码随想录链接
二叉树中的层序遍历相当于图论中的广度优先搜索;
二叉树中的递归遍历相当于图论中的深度优先搜索。
二叉树本身只有父节点和子结点之间的连接,同一层之间的节点并无连接关系,也就没办法做到层序遍历,所以要借助一个队列,保存每一层中遍历过的元素。(图论中的广度优先搜索同样是依赖队列实现的,利用队列的先进先出)

每次弹出一个节点的时候,就把这个节点的左右孩子都加进去。这时候,队列里就会有上下两层的元素,就需要用size记录每一层有多少个元素,该层的元素是否遍历完了。当把上一层的元素弹出完,此时队列中还剩的元素个数就是本层的节点个数。

/**
 * 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) {
        // 首先定义一个队列, 用于存储将要访问的节点,以按层次顺序处理它们
        queue<TreeNode*> que;
        // 不能直接把root加到队列里,首先要保证root不为空(第一个元素也有可能为空,极端情况必须考虑)
        if (root != NULL) que.push(root);
        // 最终的结果用二维数组result保存
        // 每一个内层向量表示一层的节点值
        vector<vector<int>> result;
        // 遍历二叉树,终止条件是没有元素再添加到队列里
        while (!que.empty()) {
            // size用于记录当前层节点的个数,用于控制队列中弹出的节点数量
            // 第一层节点数量为1(只有一个根节点)
            int size = que.size();
            // 定义一个一维数组,把每一层的元素放进一维数组,最终的结果应该是二维数组,包含每一层(每一层的元素存在一个一维数组);最终的结果用二维数组result保存
            vector<int> vec;
            // 这里一定要使用固定大小size,不要使用que.size(),因为que.size是不断变化的
            //通过size控制本层的元素
            for (int i = 0; i < size; i++) {
                // 获取队列前端节点
                TreeNode* node = que.front();
                // 将节点弹出队列
                que.pop();
                // 将节点记录到一维数组里
                vec.push_back(node->val);
                // 将节点的左右孩子加入队列
                if (node->left) que.push(node->left);
                if (node->right) que.push(node->right);
            }
            result.push_back(vec);
        }
        return result;
    }
};

为什么前中后序遍历用栈,层序遍历用队列???
因为,前中后序遍历需要栈的前进后出,层序遍历要利用队列的先进先出。
1.前序、中序和后序遍历都是深度优先搜索(DFS)方法。DFS 的特点是尽可能深地探索节点的子树,直到到达叶节点为止。在这些遍历方法中,栈(Stack)是一种非常合适的数据结构,因为它具有后进先出(LIFO)的特点,使用栈可以帮助我们回溯和记录访问过的节点,适合用于回溯到上一个节点。前序遍历递归实现很简单,但用栈可以避免递归的函数调用开销。
2.层序遍历(广度优先搜索,BFS)的顺序是按层次逐层访问节点,即:访问当前层的所有节点,然后再访问下一层的节点。队列(Queue)是一种适合这种访问顺序的数据结构,因为它具有先进先出(FIFO)的特点,能够保证我们按层次顺序处理节点。

二、226. 翻转二叉树(优先掌握递归)

翻转二叉树
代码随想录链接

这道题目如果想清楚就是送分题。用前序和后序最直接,中序比较绕;非递归和层序遍历方法也可以。
采用先序遍历的思路:(中左右)

  1. 确定递归函数的返回值和参数:
    因为题目要求返回新的二叉树的根节点,所以函数的返回值类型就是节点的定义类型Tree Node*;定义函数invertTree,参数为传入的根节点。
  2. 确定终止条件:遇到空节点:if (root==Null) return root;(如果根节点原本就为空,直接return)
  3. 处理逻辑:交换此节点 root的左右孩子;(root在这里不是指的根节点,而是遍历的每一个节点)
    swap(root->left,root->right);
    前序递归代码:(后序只需要把swap放在左右后面)
class Solution {
public:
    TreeNode* invertTree(TreeNode* root) {
        if (root == NULL) return root;
        swap(root->left, root->right);  // 中
        invertTree(root->left);         // 左
        invertTree(root->right);        // 右
        //  swap(root->left, root->right);  // 中 (后序)
        return root;
    }
};

后序递归代码:

class Solution {
public:
    TreeNode* invertTree(TreeNode* root) {
        if (root == NULL) return root;
        invertTree(root->left);         // 左
        swap(root->left, root->right);  // 中
        invertTree(root->left);        // 右  (注意这里依然要写->left,因为先处理了左,翻转了左,原来的左子树变成了右子树,如果再处理右,则实际上是处理了两遍左子树,而没有处理右子树)
        return root;
    }
};

迭代法前序遍历:

class Solution {
public:
    TreeNode* invertTree(TreeNode* root) {
        if (root == NULL) return root;
        stack<TreeNode*> st;
        st.push(root);
        while(!st.empty()) {
            TreeNode* node = st.top();              // 中
            st.pop();
            swap(node->left, node->right);
            if(node->right) st.push(node->right);   // 右
            if(node->left) st.push(node->left);     // 左
        }
        return root;
    }
};

迭代法前中后序统一写法代码:

class Solution {
public:
    TreeNode* invertTree(TreeNode* root) {
        stack<TreeNode*> st;
        if (root != NULL) st.push(root);
        while (!st.empty()) {
            TreeNode* node = st.top();
            if (node != NULL) {
                st.pop();
                if (node->right) st.push(node->right);  // 右
                if (node->left) st.push(node->left);    // 左
                st.push(node);                          // 中
                st.push(NULL);
            } else {
                st.pop();
                node = st.top();
                st.pop();
                swap(node->left, node->right);          // 节点处理逻辑
            }
        }
        return root;
    }
};

层序遍历(广度优先):

class Solution {
public:
    TreeNode* invertTree(TreeNode* root) {
        queue<TreeNode*> que;
        if (root != NULL) que.push(root);
        while (!que.empty()) {
            int size = que.size();
            for (int i = 0; i < size; i++) {
                TreeNode* node = que.front();
                que.pop();
                swap(node->left, node->right); // 节点处理
                if (node->left) que.push(node->left);
                if (node->right) que.push(node->right);
            }
        }
        return root;
    }
};

三、101. 对称二叉树(优先掌握递归)

题目链接
代码随想录链接
二叉树类的题目,确定遍历顺序非常重要。
本题实际上是考察二叉树是否可以翻转的问题,考察的是同时处理两个二叉树的遍历过程,同时比较两个二叉树里边对应的节点的情况.
本题只能使用后序遍历;大体思路是,转换成判断该二叉树是否可以翻转的问题,如果左右子树翻转之后能够和原来相同,则该二叉树为对称二叉树。要先把左右子树都处理完了,都返回给根节点,根节点(中)才能直到左右子树是否可以翻转。
代码实现:定义一个函数,需要传入左子树和右子树,即把根节点的左子树的头节点和右子树的头节点传入进来,判断根节点的左子树和右子树是否是可以相互翻转的,如果可以的话,整个二叉树就是对称二叉树;
首先要想,什么情况下return true,什么情况下return false.
节点的左子树为空,右子树不为空;左子树不为空,右子树为空;左右子树均为空;左右子树均不为空且值不相等;左右子树均不为空且值相等,这时,需要继续向下遍历。

class Solution {
public:
    bool compare(TreeNode* left, TreeNode* right) {
        // 首先排除空节点的情况
        if (left == NULL && right != NULL) return false;
        else if (left != NULL && right == NULL) return false;
        else if (left == NULL && right == NULL) return true;
        // 排除了空节点,不必担心空指针异常了;再排除数值不相同的情况
        else if (left->val != right->val) return false;

        // 此时就是:左右节点都不为空,且数值相同的情况
        // 此时才做递归,做下一层的判断
        // 比较二叉树外侧的节点数值是否相等,也就是左节点的左孩子,右节点的右孩子
        bool outside = compare(left->left, right->right);   // 左子树:左、 右子树:右
        // 比较二叉树内侧的节点数值是否相等,也就是左节点的右孩子,右节点的左孩子
        bool inside = compare(left->right, right->left);    // 左子树:右、 右子树:左
        // 内外侧的节点数值均相等,才能说明下面的所有孩子是可以相互翻转的
        bool isSame = outside && inside;                    // 左子树:中、 右子树:中 (逻辑处理)(所以是后序遍历)
        return isSame;

    }
    bool isSymmetric(TreeNode* root) {
        if (root == NULL) return true;
        return compare(root->left, root->right);
    }
};

看不出来前中后序遍历的简洁代码:

class Solution {
public:
    bool compare(TreeNode* left, TreeNode* right) {
        if (left == NULL && right != NULL) return false;
        else if (left != NULL && right == NULL) return false;
        else if (left == NULL && right == NULL) return true;
        else if (left->val != right->val) return false;
        else return compare(left->left, right->right) && compare(left->right, right->left);

    }
    bool isSymmetric(TreeNode* root) {
        if (root == NULL) return true;
        return compare(root->left, root->right);
    }
};

使用队列
通过队列来判断根节点的左子树和右子树的内侧和外侧是否相等,如动画所示:
在这里插入图片描述

class Solution {
public:
    bool isSymmetric(TreeNode* root) {
        if (root == NULL) return true;
        queue<TreeNode*> que;
        que.push(root->left);   // 将左子树头结点加入队列
        que.push(root->right);  // 将右子树头结点加入队列
        
        while (!que.empty()) {  // 接下来就要判断这两个树是否相互翻转
            TreeNode* leftNode = que.front(); que.pop();
            TreeNode* rightNode = que.front(); que.pop();
            if (!leftNode && !rightNode) {  // 左节点为空、右节点为空,此时说明是对称的
                continue;
            }

            // 左右一个节点不为空,或者都不为空但数值不相同,返回false
            if ((!leftNode || !rightNode || (leftNode->val != rightNode->val))) {
                return false;
            }
            que.push(leftNode->left);   // 加入左节点左孩子
            que.push(rightNode->right); // 加入右节点右孩子
            que.push(leftNode->right);  // 加入左节点右孩子
            que.push(rightNode->left);  // 加入右节点左孩子
        }
        return true;
    }
};

使用栈

细心的话,其实可以发现,这个迭代法,其实是把左右两个子树要比较的元素顺序放进一个容器,然后成对成对的取出来进行比较,那么其实使用栈也是可以的。只要把队列原封不动的改成栈就可以了.

class Solution {
public:
    bool isSymmetric(TreeNode* root) {
        if (root == NULL) return true;
        stack<TreeNode*> st; // 这里改成了栈
        st.push(root->left);
        st.push(root->right);
        while (!st.empty()) {
            TreeNode* leftNode = st.top(); st.pop();
            TreeNode* rightNode = st.top(); st.pop();
            if (!leftNode && !rightNode) {
                continue;
            }
            if ((!leftNode || !rightNode || (leftNode->val != rightNode->val))) {
                return false;
            }
            st.push(leftNode->left);
            st.push(rightNode->right);
            st.push(leftNode->right);
            st.push(rightNode->left);
        }
        return true;
    }
};

PS:真正的把题目搞清楚其实并不简单,leetcode上accept了和真正掌握了还是有距离的。
在迭代法中我们使用了队列,需要注意的是这不是层序遍历,而且仅仅通过一个容器来成对的存放我们要比较的元素,知道这一本质之后就发现,用队列,用栈,甚至用数组,都是可以的。

参考链接:
作者:力扣官方题解
链接:
https://leetcode.cn/problems/binary-tree-level-order-traversal/solutions/241885/er-cha-shu-de-ceng-xu-bian-li-by-leetcode-solution/
https://leetcode.cn/problems/symmetric-tree/solutions/268109/dui-cheng-er-cha-shu-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【机器学习】Python与深度学习的完美结合——深度学习在医学影像诊断中的惊人表现

&#x1f525; 个人主页&#xff1a;空白诗 文章目录 一、引言二、深度学习在医学影像诊断中的突破1. 技术原理2. 实际应用3. 性能表现 三、深度学习在医学影像诊断中的惊人表现1. 提高疾病诊断准确率2. 辅助制定治疗方案 四、深度学习对医疗行业的影响和推动作用 一、引言 随着…

MYSQL基础_02_MySQL环境搭建

第02章_MySQL环境搭建 1. MySQL的卸载 步骤1&#xff1a;停止MySQL服务 在卸载之前&#xff0c;先停止MySQL8.0的服务。按键盘上的“Ctrl Alt Delete”组合键&#xff0c;打开“任务管理器”对话框&#xff0c;可以在“服务”列表找到“MySQL8.0”的服务&#xff0c;如果现…

【C#线程设计】3:threadpool

实现&#xff1a; &#xff08;1&#xff09;.控件&#xff1a;group Box&#xff0c;text Box&#xff0c;check Box&#xff0c;label&#xff0c;botton&#xff0c;richtextbox 控件拉取见&#xff1a;https://blog.csdn.net/m0_74749240/article/details/139409510?spm1…

java中异常-异常概述+异常体系结构

一、异常概述 1、什么是异常&#xff1f; java程序在运行时出现的不正常情况 2、java中提供的默认的异常处理机制 java中对java程序运行时可能会出现的每种不正常情况都创建了一个唯一对应的类&#xff0c;在java程序运行时如果出现不正常情况&#xff0c;java程序就会创建…

【NOI】C++程序结构入门之循环结构三——break、continue

文章目录 前言一、循环的流程控制1.1 导入1.2 循环的打破与跳过1.2.1 break 打破1.2.2 continue 跳过1.2.3 总结 二、例题讲解问题&#xff1a;1468. 小鱼的航程问题&#xff1a;1074 - 小青蛙回来了问题&#xff1a;1261. 韩信点兵问题&#xff1a;1254. 求车速问题&#xff1…

10.结构体、共用体、枚举

头文件&#xff1a;#include<string.h> //struct&#xff1a;结构体关键字 //stu&#xff1a;结构体类型名&#xff0c;指定了一个结构体类型&#xff0c;它相当于一个模型&#xff0c;但其中并无具体数据&#xff0c;系统对之也不分配实际内存单元//使用结构体类型必须是…

【Windows】UWP - Application Frame 窗口句柄溯源

目录 一、问题描述 二、解决方案 三、测试代码 参考文献 本文出处链接&#xff1a;[https://blog.csdn.net/qq_59075481/article/details/139574981]。 一、问题描述 当 GUI 线程的窗口属于 Windows/UWP 应用程序时&#xff0c;它们始终由进程 ApplicationFrameHost 托管…

Matlab|混合策略改进的蝴蝶优化算法

目录 1 主要内容 2 部分代码 3 程序结果 4 下载链接 1 主要内容 程序主要对蝴蝶算法&#xff08;BOA&#xff09;进行改进&#xff0c;参考文献《基于改进蝴蝶优化算法的冗余机器人逆运动学求解》&#xff0c;有如下改进策略&#xff1a; 改进1&#xff1a;采用反向学习策…

2024 年最佳 iPhone 数据恢复软件

最好的 iPhone 数据恢复软件是什么&#xff1f; 说到 iPhone 数据恢复&#xff0c;拥有合适的软件对于恢复丢失或删除的文件至关重要&#xff0c;无论是照片、视频、消息、联系人还是其他重要数据。那么&#xff0c;最好的 iPhone 数据恢复软件是什么&#xff1f;有几个因素有…

信息学奥赛初赛天天练-25-CSP-J2023基础题-中序、前序与后序转换秘籍,二叉树构建、遍历技巧,以及图的拓扑排序实战应用

PDF文档公众号回复关键字:20240610 2023 CSP-J 选择题 单项选择题&#xff08;共15题&#xff0c;每题2分&#xff0c;共计30分&#xff1a;每题有且仅有一个正确选项&#xff09; 11 给定一棵二叉树&#xff0c;其前序遍历结果为&#xff1a;ABDECFG&#xff0c;中序遍历结果…

线性代数|机器学习-P11方程Ax=b求解研究

文章目录 1. 变量数和约束条件数大小分类2. 最小二乘法和Gram-schmidt变换2.1 Gram-schmidt变换2.2 最小二乘法2.2.1 损失函数-Lasso 和regression2.2.2 损失函数-Lasso2.2.3 损失函数-regression2.2.4 Regression岭回归-矩阵验证2.2.5 Regression岭回归-导数验证 3. 迭代和随机…

重新认识Word —— 制作简历

重新认识Word —— 制作简历 PPT的图形减除功能word中的设置调整页边距进行排版表格使用 我们之前把word长排版文本梳理了一遍&#xff0c;其实word还有另外的功能&#xff0c;比如说——制作简历。 在这之前&#xff0c;我们先讲一个小技巧&#xff1a; PPT的图形减除功能 …

Elasticsearch:Open Crawler 发布技术预览版

作者&#xff1a;来自 Elastic Navarone Feekery 多年来&#xff0c;Elastic 已经经历了几次 Crawler 迭代。最初是 Swiftype 的 Site Search&#xff0c;后来发展成为 App Search Crawler&#xff0c;最近又发展成为 Elastic Crawler。这些 Crawler 功能丰富&#xff0c;允许以…

Typora Markdown编辑器 for Mac v1.8.10 安装

Mac分享吧 文章目录 效果一、准备工作二、开始安装1、双击运行软件&#xff0c;将其从左侧拖入右侧文件夹中&#xff0c;等待安装完毕2. 应用程序显示软件图标&#xff0c;表示安装成功 三、运行调试1、修改主题2、显示文档列表&#xff0c;如下图3、查看版本信息 **安装完成&…

LearnDash+BuddyBoss:终极在线课程社区组合

您是否希望使用 WordPress 建立在线课程社区&#xff1f; 如果是这样&#xff0c;没有比LearnDash和BuddyBoss在线课程社区更好的组合了。使用这两款产品&#xff0c;您可以创建和销售在线课程、创建群组和讨论&#xff0c;并为您的学生提供整个社交网络&#xff0c;所有这些都…

CUDA 编程(1):使用Grid 和 Block分配线程

1 介绍 1.1 Grid 和 Block 概念 核函数以线程为单位进行计算的函数,cuda编程会涉及到大量的线程(thread),几千个到几万个thread同时并行计算,所有的thread其实都是在执行同一个核函数。 对于核函数(Kernel),一个核函数一般会分配1个Grid, 1个Grid又有很多个Block,1个Bloc…

【python】python GUI编程--tkinter模块初探

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

【大学物理】波动光学:光的衍射

大学物理(上) 可视化详解学习&#xff01;| 第五讲 | 31分钟学习 光的干涉与衍射_哔哩哔哩_bilibili 第13章 波动光学-4 光的衍射-1 单缝衍射_哔哩哔哩_bilibili 0 definition 衍射也是波的性质之一&#xff0c;指的是波遇到障碍物时不再沿直线传播&#xff0c;进入障碍物背…

贪吃蛇双人模式设计(2)

敲上瘾-CSDN博客控制台程序设置_c语言控制程序窗口大小-CSDN博客贪吃蛇小游戏_贪吃蛇小游戏csdn-CSDN博客​​​​​​​ 一、功能实现&#xff1a; 玩家1使用↓ → ← ↑按键来操作蛇的方向&#xff0c;使用右Shift键加速&#xff0c;右Ctrl键减速玩家2使用W A S D按键来操…

NFT 智能合约实战-快速开始(1)NFT发展历史 | NFT合约标准(ERC-721、ERC-1155和ERC-998)介绍

文章目录 NFT 智能合约实战-快速开始(1)NFT发展历史国内NFT市场国内NFT合规性如何获得NFT?如何查询NFT信息?在 OpenSea 上查看我们的 NFT什么是ERC721NFT合约标准ERC-721、ERC-1155和ERC-998 对比ERC721IERC721.sol 接口内容关于合约需要接收 ERC721 资产 onERC721Received…