今天我来分享一篇关于二叉树的文章(建议收藏,便于巩固基础)。

  • 看完此文leetcode至少解决八道题

  • 掌握二叉树的前序、中序、后序遍历以及两种不同的实现方式:递归与非递归

  • 非递归时遍历与层次遍历时,有详细的图解表示队列/栈中的元素是如何移动的,有助于理解代码的运行

二叉树介绍

二叉树(binary tree) 是指树中节点的度不大于2的有序树,它是一种最简单且最重要的树。

二叉树的递归定义为: 二叉树是一棵空树,或者是一棵由一个根节点和两棵互不相交的,分别称作根的左子树和右子树组成的非空树;左子树和右子树又同样都是二叉树

  • 逻辑上二叉树有五种基本形态,如图所示

  1. 空二叉树

  2. 只有一个根结点的二叉树

  3. 只有左子树

  4. 完全二叉树

  5. 只有右子树

二叉树相关属性解释:

  • 结点:包含一个数据元素及若干指向子树分支的信息。

  • 结点的度:一个结点拥有子树的数目称为结点的度。

  • 叶子结点:也称为终端结点,没有子树的结点或者度为零的结点。

  • 分支结点:也称为非终端结点,度不为零的结点称为非终端结点。

  • 树的度:树中所有结点的度的最大值。

  • 结点的层次:从根结点开始,假设根结点为第1层,根结点的子节点为第2层,依此类推,如果某一个结点位于第L层,则其子节点位于第L+1层。

  • 树的深度:也称为树的高度,树中所有结点的层次最大值称为树的深度。

  • 有序树:如果树中各棵子树的次序是有先后次序,则称该树为有序树。

  • 无序树:如果树中各棵子树的次序没有先后次序,则称该树为无序树。

二叉树遍历方式

  • 二叉树遍历方式分为三种

    • 前序遍历(根左右):访问根结点,再访问左子树、再访问右子树。

    • 中序遍历(左根右):先访问左子树,再访问根结点、再访问右子树。

    • 后续遍历(左右根):先访问左子树,再访问右子树,再访问根结点。

例如一个这个样子的二叉树,按三种遍历方法分别遍历,输出的结果分别是

  • 前序遍历:ABDECFG

  • 中序遍历:DBEAFCG

  • 后续遍历:DEBFGCA

下面我们一起来用代码实现下这三种遍历

  • 注:以上前序、中序、后序每一种遍历方式都有递归非递归两种实现方法

  • 前序遍历就是深度优先遍历(DFS)

  • 层次遍历就是广度优先遍历(BFS)

二叉树递归遍历

* 前序遍历 (LeetCode  144)


class Solution {//声明列表ArrayList<Integer> list = new ArrayList<>();public List<Integer> preorderTraversal(TreeNode root) {// 如果根节点为空,则直接返回空列表if (root == null){return  new ArrayList<>();}//节点不为空,将节点的值添加进列表中    list.add(root.val);//判断此节点的左节点是否为空,如果不为空则将递归遍历左子树if (root.left != null){preorderTraversal(root.left);}//判断此节点的右节点是否为空,如果不为空则将递归遍历右子树if (root.right != null){preorderTraversal(root.right);}//最后返回列表return list;}
}
  • 中序遍历(LeetCode  94)


class Solution {//声明列表ArrayList<Integer> list = new ArrayList<>();public List<Integer> inorderTraversal(TreeNode root) {// 如果根节点为空,则直接返回空列表if (root == null){return  new ArrayList<>();}//判断此节点的左节点是否为空,如果不为空则将递归遍历此节点的左子树if (root.left != null){inorderTraversal(root.left);}//节点不为空,将节点的值添加进列表中list.add(root.val);//判断此节点的右节点是否为空,如果不为空则将递归遍历此节点的右子树if (root.right != null){inorderTraversal(root.right);}//最后返回列表return list;}
}
  • 后续遍历(LeetCode  145)


class Solution {//声明列表ArrayList<Integer> list = new ArrayList<>();public List<Integer> postorderTraversal(TreeNode root) {// 如果根节点为空,则直接返回空列表if (root == null){return  new ArrayList<>();}//判断此节点的左节点是否为空,如果不为空则将递归遍历此节点的左子树if (root.left != null){postorderTraversal(root.left);}//判断此节点的右节点是否为空,如果不为空则将递归遍历此节点的右子树if (root.right != null){postorderTraversal(root.right);}//节点不为空,将节点的值添加进列表中list.add(root.val);//最后返回列表return list;}
}

我们通过观察发现,这代码怎么这么像,是的就是很像,他们唯一的区别就是list.add(root.val);代码的位置不一样,这行代码就代表文中的 遍历(访问)
下图中为前序遍历(左右)

下图中为中序遍历(左右)

下图中为后序遍历(左右

二叉树非递归遍历

  • 用到栈(FILO 先进后出的特性)

  • 每段代码后,都有栈和其中元素的关系具体过程,建议静下心来慢慢看,有助于理解代码如何运行

  • 前序遍历


class Solution {List list =   new ArrayList();public List<Integer> preorderTraversal(TreeNode root) {//如果根节点为空,则直接返回空列表if(root==null){return  new ArrayList();}//声明一个栈Stack<TreeNode> stack = new Stack<>();//将节点入栈stack.push(root);//如果栈不为空while (!stack.empty()){//从栈弹出这个节点TreeNode node = stack.pop();//添加进列表中list.add(node.val);// 如果这个节点的右子节点不为空if (node.right!=null){// 将其入栈  因为栈是先进后出,所以先压栈右子节点  后出stack.push(node.right);}// 如果这个节点的左子节点不为空if (node.left!=null){// 将其入栈 因为栈是先进后出,所以后压栈左子节点 先出}}//返回列表return list;}
}

  • 中序遍历


class Solution {public List<Integer> inorderTraversal(TreeNode root) {//判断节点是否为空,为空的话直接返回空列表if (root == null){return new ArrayList();}//声明列表存储结果List<Integer> list =  new ArrayList();//声明一个栈Stack<TreeNode> stack = new Stack<>();//当节点不为空或者栈不为空时while (root != null || !stack.empty()){//当节点不为空时while (root != null){//将节点压栈stack.push(root);//将节点指向其左子节点root = root.left;}//如果栈不为空if (!stack.empty()){//将栈里元素弹出来TreeNode node = stack.pop();//添加进列表中list.add(node.val);//将节点指向其右子节点root = node.right;}}return list;}
}

  • 后序遍历


class Solution {public List<Integer> postorderTraversal(TreeNode root) {// 如果根节点为空,则直接返回空列表if (root == null){return  new ArrayList<>();}//声明列表ArrayList<Integer> list = new ArrayList<>();//声明栈AStack<TreeNode> stackA = new Stack<TreeNode>();//声明栈BStack<TreeNode> stackB = new Stack<TreeNode>();//将次元素压入栈AstackA.push(root);//当栈A不为空时while (!stackA.empty()){//取出其中压入的元素TreeNode node = stackA.pop();//压入栈B中stackB.push(node);//当此节点左子节点不为空时if (node.left != null){//压入栈AstackA.push(node.left);}//当此节点右子节点不为空时if (node.right != null){//压入栈AstackA.push(node.right);}}//当栈B不为空时while (!stackB.empty()){//取出其元素并且添加至列表中TreeNode node = stackB.pop();list.add(node.val);}//最后返回列表return list;}
}

二叉树层序遍历(BFS)

  • LeetCode  102   二叉树的层序遍历

  • 用到队列(FIFO 先进先出的特性)代码后有队列和其中元素的关系具体过程,建议静下心来慢慢看,有助于理解代码如何运行

class Solution {public List<List<Integer>> levelOrder(TreeNode root) {if (root == null) {return new ArrayList<List<Integer>>();}// 声明一个列表存储每一行的数据List<List<Integer>> result = new ArrayList<>();//声明一个队列LinkedList<TreeNode> queue = new LinkedList<>();//如果根节点不为空,将其入队queue.offer(root);//当队列不为空时,代表队列里有数据while (!queue.isEmpty()) {//存储每一行的数据lineList<Integer> line = new ArrayList<Integer>();//保存队列中现有数据的个数,这些就是要添加至每一行列表的值int size = queue.size();for (int i=0;i<size;i++){//取出队列的节点 (FIFO 先进先出)TreeNode node = queue.poll();line.add(node.val);if (node.left != null) {queue.offer(node.left);}if (node.right != null) {queue.offer(node.right);}}result.add(line);}return result;}
}

leetcode二叉树相关练习

  • 我们看到了这里,对二叉树的前序(DFS)、中序、后序、递归/非递归以及层次遍历(BFS)都有了一定的了解(如果上面的图都消化了的话

然后我们趁热打铁来几道leetcode题目试试手!(总体代码和上面只有稍微的改动,因为大致思想是一样的,把上面的内容都消化了的话就很简单啦)

  • leetcode-257 二叉树的所有路径


class Solution {public List<String> binaryTreePaths(TreeNode root) {if (root == null){return new ArrayList<>();}ArrayList<String> list = new ArrayList<>();Stack<TreeNode> stack = new Stack<TreeNode>();//这个栈存储路径,与上一个存储节点的栈一样的操作Stack<String> path = new Stack<String>();stack.push(root);path.push(root.val+"");while (!stack.empty()){TreeNode node = stack.pop();String p = path.pop();//当是叶子节点的时候,此时栈中的路径即为一条完整的路径,可以加入到结果中if (node.right == null && node.left == null ){list.add(p);}//如果右子节点不为空if (node.right != null){stack.push(node.right);//将临时路径继续压栈path.push(p+"->"+node.right.val);}//如果左子节点不为空if (node.left != null){stack.push(node.left);//将临时路径继续压栈path.push(p+"->"+node.left.val);}}return list;}
}
  • leetcode-104 二叉树的最大深度 与 剑指offer 55-I 相同


class Solution {public int maxDepth(TreeNode root) {if (root == null){return 0;}LinkedList<TreeNode> queue = new LinkedList<>();int result = 0;queue.offer(root);while (!queue.isEmpty()){//层数+1result++;//这是当前层的节点的个数int size = queue.size();for (int i=0;i<size;i++){//要将其全部出队后,才可以再次计数TreeNode node = queue.poll();if (node.left != null){//如果出队的节点还有左子节点,就入队queue.offer(node.left);}if (node.right != null){//如果出队的节点还有右子节点,就入队queue.offer(node.right);}}}//返回层数return result;}
}
  • leetcode-107 二叉树的层序遍历2


class Solution {public List<List<Integer>> levelOrderBottom(TreeNode root) {if (root == null){return new ArrayList<List<Integer>>() ;}List<List<Integer>> result = new ArrayList<List<Integer>>() ;LinkedList<TreeNode> queue = new LinkedList<>();//声明一个栈,用来存储每一层的节点Stack<ArrayList<Integer> > stack = new Stack<>();queue.offer(root);while (!queue.isEmpty()){int size = queue.size();ArrayList<Integer> list = new ArrayList<>();for (int i=0;i<size;i++){TreeNode node = queue.poll();list.add(node.val);if (node.left != null){queue.offer(node.left);}if (node.right != null){queue.offer(node.right);}}//将这一层的节点压入栈中stack.push(list);}//当栈不为空时,弹出结果,从而达到从下往上遍历二叉树的效果while (!stack.isEmpty()){ArrayList<Integer> list = stack.pop();result.add(list);}return result;}
}

推荐关注

二叉树的各种遍历方式,我都帮你总结了,附有队列堆栈图解相关推荐

  1. 剑指offer——复习1:二叉树三种遍历方式的迭代与递归实现

    剑指offer--复习1:二叉树三种遍历方式的迭代与递归实现 20180905更新:这个博客中的解法不是很好,看相应的LeetCode题目笔记~~~ 我感觉此博客中的说法更容易让人理解:https:/ ...

  2. 二叉树三种遍历方式,先序、中序、后序

    二叉树遍历方式分为三种:先序,中序和后序. 可以以根节点的位置为参考来记遍历方式,在第一个为先序,中间为中序,最后为后序: 即:先序: 根左右:中序:左根右:后序:左右根. 借个图: 之前看过一个视频 ...

  3. 二叉树三种遍历方式的非递归实现

    树的递归实现方式很简单,下面介绍三种遍历的非递归实现. 树的遍历有个特点,那就是在处理具体问题时,绝大多数情况下是在当前循环.或函数(或是子树)的根节点来处理的,能够注意到当前根节点是如何从上个根节点 ...

  4. 二叉树的四种遍历方式(前序遍历、中序遍历、后序遍历、测层序遍历)

    一.二叉树的遍历 遍历是数据结构中的常见的操作,把所有元素都访问一遍. 线性数据结构的遍历比较简单 ①.正序遍历 ②.逆序遍历 根据节点访问顺序的不同,二叉树的常见遍历方式用四种 ①.前序遍历(Pre ...

  5. 二叉树的三种遍历方式(递归、非递归和Morris遍历)

    二叉树的三种遍历方式(递归.非递归和Morris遍历) 原文:http://www.linuxidc.com/Linux/2015-08/122480.htm 二叉树遍历是二叉树的最基本的操作,其实现 ...

  6. 二叉树的前序中序后序三种遍历方式及递归算法介绍

    二叉树三种遍历方式 二叉树的遍历是整个二叉树的核心,二叉树的几本操作都要依赖于遍历,对于二叉树的遍历,递归是最简单也最容易理解的,本文详细介绍了二叉树的三种遍历方法,并用递归来实现: 完整的可调试代码 ...

  7. 刷题:二叉树的非递归遍历方式

    二叉树的非递归的遍历方式 上篇博客记录了二叉树的递归遍历方式以及根据二叉树的遍历结果还原二叉树的内容. 本篇博客记录二叉树的非递归的遍历方式. 二叉树的非递归遍历需要借助栈来实现,而且三种遍历的方式的 ...

  8. 【算法笔记】二叉树之基础遍历

    基础知识 一. 二叉树的种类 二叉树主要分为满二叉树.完全二叉树 满二叉树:如果一棵二叉树只有度为0的结点和度为2的结点,并且度为0的结点在同一层上,则这棵二叉树为满二叉树.深度为k的满二叉树,有2k ...

  9. 二叉树非递归遍历的一点理解

    二叉树是我们必须要了解的一个数据结构,针对这个数据结构我们可以衍生出好多知识. 主要是有几种遍历方式,前序遍历,中序遍历,后续遍历,层次遍历. 下面我们根据这个图来详细的说一下这几种非递归遍历的思想与 ...

最新文章

  1. c++ socket error 10038错误
  2. 如何判断数组是静态还是动态分配的
  3. 关于ajax 1.0的一个问题?
  4. matlab谢尔宾斯三角_城市的公式
  5. [silverlight基础]仿文字连接跑马灯效果-高手绕道
  6. python saltstack web_saltstack学习-8:web管理页面(halite)
  7. python 八大排序_八大排序算法的 Python 实现
  8. 5年前我在博客中写的三目运算符的空指针问题,终于被阿里巴巴开发手册收录了。...
  9. html基础标签 1211
  10. react jest测试_如何设置Jest和Enzyme来测试React Native应用
  11. Linux***检测基础学习
  12. 功能拆分简化权限操作,提高程序易用性
  13. Multisim 编码器 译码器 74LS138 74LS148
  14. python 以图搜图百度_基于opencv的图片检索(模仿百度的以图搜图功能)
  15. 汽车颗粒物排放对环境和健康的影响及政府监管策略
  16. Mysql事务操作及存储引擎
  17. 论文笔记(五)FWENet:基于SAR图像的洪水水体提取深度卷积神经网络(CVPR)
  18. C++实践 极简版本贪吃蛇小游戏
  19. 学会做风格化游戏3D场景,关键是要会 “抓特征”
  20. ucos系统使用delay函数死机原因

热门文章

  1. ac在计算机上是什么作用,ac+是什么
  2. 土的渗透系数计算c语言程序,岩土层渗透系数K的经验值
  3. 我的Android重构之旅:插件化改造及原理
  4. python爬虫爬微信数据可信吗_Python爬取微信,我发现了之前没发现的秘密!
  5. 什么是electron?
  6. 双路主板能不能上两个不同型号的CPU?
  7. OSError: [Errno 22] Invalid argument错误解决方案
  8. (8)UVM 学会消息管理才会让你在验证中游刃有余
  9. Docker Swarm集群搭建以及服务命令等操作
  10. mysql exists和in