二叉树基础

二叉树理论基础,这里直接贴出来代码随想录的地址先,然后再对自己认为比较重要的地方回顾一遍。

  • 首先是二叉树,二叉树是一种数据结构,其中注意一下完全二叉树:除了最底层的结点没有填满,底层以上的结点都是满的,并且没有填满的底层结点一定是其父节点的左孩子。

  • 当二叉树的结点储存数据时,他就变成了二叉搜索树(Binary Search Tree),一颗二叉搜索树是有序的,他有以下规则:

    1. 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值
    2. 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值
    3. 它的左、右子树也分别为二叉排序树

    注意是左子树和右子树全都大于或小于根节点的值,而不是一个左孩子或右孩子。

  • 二叉搜索树不一定是满的,而且最小深度的叶子结点和最大深度的叶子结点之间的差值没有要求,这里就引出了平衡二叉搜索树也就是AVL。AVL有以下性质:他的左右两个子树之间的高度差不大于1,并且两个子树都是平衡二叉树。贴上卡哥的图:

    C++中map、set、multimap,multiset的底层实现都是平衡二叉搜索树,所以map、set的增删操作时间时间复杂度是logn。其实严格来说是红黑树,也是AVL的一种,性能上来说AVL更适合查找,红黑树更适合增删。(之前看的红黑树都忘得差不多了,具体感兴趣的小伙伴可以看一下三太子的这篇文章:硬核图解面试最怕的红黑树)。

二叉树的遍历方式

遍历方式分为两种:深度优先遍历广度优先遍历

  • 深度优先遍历(DFS)
    深度优先就是从根节点开始,一直向下遍历,直到遇到叶子结点。分为三种顺序:前序中序后序遍历。遍历的方法分为:迭代递归
    还是贴出卡哥的图吧,前中后表示的是中间结点遍历时的顺序。
  • 广度优先遍历(BFS)
    广度优先就是一层一层的遍历,比如上图二叉树的遍历为: 5 4 6 1 2 7 8。很好理解,广度优先遍历时一般是借助queue队列来遍历。

最后说一下二叉树再c++中的定义:

struct TreeNode {int val;TreeNode *left;TreeNode *right;TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};

二叉树的递归遍历

递归遍历首先要确定递归三要素:

  1. 递归函数的输入输出
  2. 递归的终止条件
  3. 递归的函数体

首先,递归遍历的过程中需要把遍历的元素存入数组中,所以输入参数有一个数组,遍历时自然有一个结点,所以输入有两个参数;递归逻辑中不需要使用返回值作为递归条件,所以函数类型为void。其次,当输入结点为NULL时,本层的递归结束,所以终止条件为if(cur == NULL)。最后就是递归的逻辑了。
递归是深度优先遍历的一种,所以有前中后序三种顺序,以前序为例:
前序遍历中结点的顺序为:中左右,所以在写递归逻辑时,首先需要将当前结点存入数组再去遍历他的左右孩子。
走到叶子结点时,它是没有子结点的,所以叶子结点可以看成是左右孩子为NULL的子树中的中间结点,存入数组后遍历NULL的结点就会跳出这一层的递归。
前序遍历的递归函数如下:

void traversal(TreeNode* cur , vector<int>& ans){if(cur == NULL) return;ans.push_back(cur->val);traversal(cur->left , ans);traversal(cur->right , ans);
}

中序遍历的结点顺序为:左中右。我的理解是当遍历一个结点时,先去找他的左孩子,直到当前结点为叶子节点时,他就是和他的父节点构成的子树中的“”,而他是与两个NULL结点组成的子树中的中,空结点自然不会被输入到数组中,所以跳出递归时,他被输入到数组中。当叶子结点跳出递归时,处理“”,直接输入进数组,再去遍历右孩子。
所以中序遍历的递归函数如下:

void traversal(TreeNode* cur , vector<int>& ans){if(cur == NULL) return;traversal(cur->left , ans);ans.push_back(cur->val);traversal(cur->right , ans);
}

代码只是改变了顺序,还是要加深一下理解。
最后就是后序遍历了,同理:

void traversal(TreeNode* cur , vector<int>& ans){if(cur == NULL) return;traversal(cur->left , ans);traversal(cur->right , ans);ans.push_back(cur->val);

最后一步处理中间结点。
需要注意的是:递归函数中的数组需要传入地址vector<int>& ans

二叉树的迭代遍历

二叉树的迭代遍历需要使用额外容器来存放结点,额外的容器使用stack
在写迭代之前,首先使用栈来模拟一下遍历的顺序,搞清楚何时入栈何时出栈,剩下的就是细节的处理,比如说对空结点的判断。

  • 前序
    首先处理结点,处理完中结点之后,将其左右孩子压入栈中,需要注意一下顺序,因为栈是先进后出的,所以入栈时应该先压入右孩子,再压入左孩子。代码如下:

    vector<int> preorderTraversal(TreeNode* root) {stack<TreeNode*> st;vector<int> ans;if(root != NULL) st.push(root);while(!st.empty()){TreeNode* cur = st.top();st.pop();ans.push_back(cur->val);if(cur->right) st.push(cur->right);if(cur->left) st.push(cur->left);}return ans;
    }
    

    主要还是模拟的过程。

  • 后序
    后序采用一个比较巧的方式,只要将前序中左右孩子的入栈顺序交换,最后反转一下结果数组即可。
    代码如下:

    vector<int> postorderTraversal(TreeNode* root) {stack<TreeNode*> st;vector<int> result;if (root == NULL) return result;st.push(root);while (!st.empty()) {TreeNode* node = st.top();st.pop();result.push_back(node->val);if (node->left) st.push(node->left); // 相对于前序遍历,这更改一下入栈顺序 (空节点不入栈)if (node->right) st.push(node->right); // 空节点不入栈}reverse(result.begin(), result.end()); // 将结果反转之后就是左右中的顺序了return result;
    }
    
  • 中序
    中序和前序不一样,因为遍历的结点和需要处理输出的结点不是同一个。中序是先找到底层结点之后再开始处理输出,那么当cur为空时,说明已经遍历到了底层,这时候再开始处理输出。
    代码如下:

    vector<int> inorderTraversal(TreeNode* root) {stack<TreeNode*> st;vector<int> ans;TreeNode* cur = root;while(cur != NULL || !st.empty()){if(cur != NULL){//左st.push(cur);cur = cur->left;}else{//中cur = st.top(); st.pop();ans.push_back(cur->val);cur = cur->right; //右}}return ans;
    }
    

    cur == NULL时,此时栈顶就是需要处理的结点。while循环中的cur != NULL是为了防止根节点的左子树为NULL的情况,此时栈中为空,但是cur保存的是右孩子。

  • 迭代的统一写法
    迭代的前中后序因为遍历的顺序和处理结点的顺序不同导致写法有差异,所以改变写法使其变成像递归一样只需要改变顺序即可。这里的思路是:当遇到需要处理的结点时,暂时不处理,而是在栈中插入一个NULL给他标记上,再继续遍历后续结点;当栈弹出,cur == NULL时,再处理结点。
    统一写法下的中序迭代遍历代码:

    vector<int> inorderTraversal(TreeNode* root) {vector<int> ans;stack<TreeNode*> st;if(root != NULL) st.push(root);while(!st.empty()){TreeNode* cur = st.top();if(cur != NULL){st.pop();if(cur->right) st.push(cur->right); //右st.push(cur);  //中st.push(NULL);if(cur->left)   st.push(cur->left); //左}else{st.pop();ans.push_back(st.top()->val);st.pop();}}return ans;
    }
    

    有几点需要注意:

    • 可以看到入栈的顺序不是左中右而是右中左
    • 第一个pop操作应该在if(cur != NULL)里面,否则会造成读取空指针错误的情况
    • 这样写的顺序会先把二叉树整个遍历一遍,才开始处理结点,这样也符合中序遍历的逻辑。

    统一写法下的后序迭代遍历代码:

    vector<int> inorderTraversal(TreeNode* root) {vector<int> ans;stack<TreeNode*> st;if(root != NULL) st.push(root);while(!st.empty()){TreeNode* cur = st.top();if(cur != NULL){st.pop();st.push(cur);  //中st.push(NULL);if(cur->right) st.push(cur->right); //右if(cur->left)   st.push(cur->left); //左}else{st.pop();cur = st.top();st.pop();result.push_back(cur->val);}}return ans;
    }
    

    这里入栈的顺序是中右左,目的就是把中结点压在最里面最后处理。
    统一写法下的前序迭代遍历代码:

    vector<int> preorderTraversal(TreeNode* root) {vector<int> ans;stack<TreeNode*> st;if(root != NULL) st.push(root);while(!st.empty()){TreeNode* cur = st.top();if(cur != NULL){st.pop();if(cur->right) st.push(cur->right);if(cur->left) st.push(cur->left);st.push(cur);st.push(NULL);}else{st.pop();ans.push_back(st.top()->val);st.pop();}}return ans;
    

    同样,这里的入栈顺序不是中左右,而是右左中

迭代的统一遍历一定要先模拟入栈和出栈!!
否则很容易就绕晕了。

代码随想录第十四天 二叉树基础 LeetCode 144、145、94相关推荐

  1. 代码随想录 第二十四天 17.电话号码的字母组合||216.组合总和III

    216.组合总和III 力扣题目链接(opens new window) 找出所有相加之和为 n 的 k 个数的组合.组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字. 说明: ...

  2. 假赋值java_Java十四天零基础入门-Java赋值运算符

    不闲聊!!!不扯淡!!!小UP只分享Java相关的资源干货 Java赋值运算符 赋值运算符目前也是只需要掌握=.+=.-=.*=./=.%=,其它和二进制相关的内容也是到后面遇到的时候再详细学习.赋值 ...

  3. Java从键盘输入n行字符串_Java十四天零基础入门-Java布尔类型

    不闲聊!!!不扯淡!!!小UP只分享Java相关的资源干货 Java布尔类型 在Java语言中布尔类型的值只包括true和false,没有其他值,不包括1和0,布尔类型的数据在开发中主要使用在逻辑判断 ...

  4. java程序员从笨鸟到菜鸟_Java程序员从笨鸟到菜鸟之(十四)Html基础积累总结(上)...

    [新朋友]点击标题下面(↑)蓝色字"Java那些事"关注 [老朋友]点击右上角,转发或分享本页面内容 这是我以前写的<java程序员由笨鸟到菜鸟>系列博客,每天更新一篇 ...

  5. x264代码剖析(十四):核心算法之宏块编码函数x264_macroblock_encode()

    x264代码剖析(十四):核心算法之宏块编码函数x264_macroblock_encode() 宏块编码函数x264_macroblock_encode()是完成变换与量化的主要函数,而x264_m ...

  6. java短除法获取二进制_Java十四天零基础入门-Java的数据类型介绍

    不闲聊!!!不扯淡!!!小UP只分享Java相关的资源干货 本章节目标: 理解数据类型的作用.Java中包括哪些数据类型?常见的八种基本数据类型都有哪些?会用八种基本数据类型声明变量?什么是二进制?原 ...

  7. 信息系统项目管理师核心考点(六十四)信息安全基础知识重要概念

    科科过为您带来软考信息系统项目管理师核心重点考点(六十四)信息安全基础知识重要概念,内含思维导图+真题 [信息系统项目管理师核心考点]信息安全基础知识重要概念 1.加密技术 ①对称加密[加密与解密的秘 ...

  8. mysql循环查询一个表中的数据并进行修改_JavaScript学习笔记(二十四)-- MYSQL基础操作...

    MYSQL mysql 是一个数据库的名字 和 php 合作的比较好的数据库 之前我们说过一个问题,前端向后端索要数据,后端就是去数据库中查询数据,返回给前端 接下来就聊聊使用 php 操作数据库 M ...

  9. 代码随想录第二十五天|组合、电话号码的字母组合

    代码随想录第二十五天|216.17不熟 Leetcode 216. 组合总和 III Leetcode 17. 电话号码的字母组合 Leetcode 216. 组合总和 III 题目链接: 组合总和 ...

  10. 刷题笔记(十四)--二叉树:层序遍历和DFS,BFS

    目录 系列文章目录 前言 题录 102. 二叉树的层序遍历 BFS DFS_前序遍历 107. 二叉树的层序遍历 II BFS DFS 199. 二叉树的右视图 BFS DFS 637. 二叉树的层平 ...

最新文章

  1. 详解Spring中Bean的自动装配~
  2. IOS开发UISearchBar失去第一响应者身份后,取消按钮不执行点击事件的问题
  3. rhel5+nis+autofs+nfs
  4. 关于CSS样式清除浮动的总结
  5. cmd:计算机cmd常用命令集合之详细攻略daiding
  6. 转:c# 根据当前时间获取,本周,本月,本季度,月初,月末,各个时间段
  7. java 获取当前目录_java获得当前文件路径
  8. Kafka系统的组件、角色以及和zookeeper的关系
  9. C#增删改查操作Access数据库之二(数据库的增加)
  10. COM.MYSQL.JDBC.DRIVER 和 COM.MYSQL.CJ.JDBC.DRIVER的区别
  11. 跨境电商为什么要用ERP系统?
  12. xulrunner弹出窗口不显示解决方法
  13. codeforces 796A-D
  14. 我的Android进阶之旅------Android ListView优化详解
  15. 基于深度学习生成音乐(mid格式)
  16. 殇雪计算机谱子,殇雪简谱(歌词)-云菲菲演唱-桃李醉春风记谱
  17. BASE64Encoded() 方法报错说方法未定义
  18. SAP 新配置公司代码F-02记账时报错“通用日记账的分类账定制设置中存在不一致” 解决
  19. 361度与国棉联盟达成战略合作;开利宣布收购空调企业广东积微集团;大陆集团与地平线成立智能驾驶合资公司 | 美通企业周刊...
  20. 【yoyo】类,对象,方法,属性,事件的定义

热门文章

  1. python文本文件操作诗句给上一句输出下一句_使用RNN生成文本实战:莎士比亚风格诗句...
  2. eNews 第二十四期/2007.05
  3. staruml 依赖于 libgcrypt11 (= 1.4.5);然而:未安装软件包 libgcrypt11。
  4. 文献阅读_Radiogenomic analysis of vascular endothelial growth factor in patients with diffuse gliomas
  5. 虚拟机配置opc服务器,组态王怎么配置成opc服务器
  6. Python学习_038.列表_排序_revered逆序_max_min_sum
  7. m3u8视频格式转换
  8. Unable to find instance for system
  9. 海康监控如何设置STMP邮箱报警
  10. 74HC04六通道反相器介绍