基于Java的二叉树的三种遍历方式的递归与非递归实现
二叉树的遍历方式包括前序遍历、中序遍历和后序遍历,其实现方式包括递归实现和非递归实现。
前序遍历:根节点 | 左子树 | 右子树
中序遍历:左子树 | 根节点 | 右子树
后序遍历:左子树 | 右子树 | 根节点
1. 递归实现
递归方式实现代码十分简洁,三种遍历方式的递归实现代码结构相同,只是执行顺序有所区别。
前序遍历:
public class preOrderRecur {
List<Integer> res = new ArrayList<>();
public List<Integer> preOrderTraversal(TreeNode root) {
if (root != null) {
res.add(root.val); // 根节点
preOrderTraversal(root.left); // 左子树
preOrderTraversal(root.right); // 右子树
}
return res;
}
}
中序遍历:
public class inOrderRecur {
List<Integer> res = new ArrayList<>();
public List<Integer> inOrderTraversal(TreeNode root) {
if (root != null) {
inOrderTraversal(root.left); // 左子树
res.add(root.val); // 根节点
inOrderTraversal(root.right); // 右子树
}
}
return res;
}
后序遍历:
public class inOrderRecur {
List<Integer> res = new ArrayList<>();
public List<Integer> inOrderTraversal(TreeNode root) {
if (root != null) {
inOrderTraversal(root.left); // 左子树
inOrderTraversal(root.right); // 右子树
res.add(root.val); // 根节点
}
}
return res;
}
2. 迭代实现
2.1 使用辅助栈——空间复杂度O(N)
2.1.1 中序遍历
- 从当前结点一直向其最左孩子搜索,直到没有左孩子了停止,这个过程中将路程中的所有结点入栈;
- 弹出栈顶元素,将其记录在答案中,并把当前结点置为弹出元素的右孩子并重复第一步过程。
public class inOrderIterator {
List<Integer> res = new ArrayList<>();
public List<Integer> inOrderTraversal(TreeNode root) {
Stack<TreeNode> stack = new Stack<>();
while (root != null || !stack.isEmpty()) {
if (root != null) {
stack.push(root);
root = root.left;
} else {
TreeNode node = stack.pop();
res.add(node.val);
root = node.right;
}
}
return res;
}
}
2.1.2 前序遍历
方法1:因为前序遍历访问顺序是“中-左-右”,所以可以先将根结点压栈,然后按照下列步骤执行。
- 如果栈不为空,则弹出栈顶元素存入结果中;
- 如果弹出元素的右孩子不为空则将右孩子压栈,然后如果其左孩子也不为空将其左孩子压栈(因为栈是后入先出的,所以为了达到“中-左-右”的顺序,需要先压入右孩子,再压入左孩子)。
public class preOrderIterator {
List<Integer> res = new ArrayList<>();
public List<Integer> inOrderTraversal(TreeNode root) {
if (root == null) return res;
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()) {
root = stack.pop();
res.add(root.val);
// 右孩子压栈
if (root.right != null) stack.push(root.right);
// 左孩子压栈
if (root.left != null) stack.push(root.left);
}
return res;
}
}
方法2:根据中序遍历进行微调:
public class preOrderIterator {
List<Integer> res = new ArrayList<>();
public List<Integer> inOrderTraversal(TreeNode root) {
Stack<TreeNode> stack = new Stack<>();
while (root != null || !stack.isEmpty()) {
if (root != null) {
res.add(root.val);
stack.push(root);
root = root.left;
} else {
TreeNode node = stack.pop();
root = node.right;
}
}
return res;
}
}
2.1.3 后序遍历
因为前序遍历的顺序是“左-中-右”,而后序遍历顺序是“左-右-中”,不考虑左结点,区别只是在于中结点和右结点的顺序进行了反向而已,因此可以使用前序遍历的代码进行调整,只需要将前序遍历对左右孩子压栈的顺序反向即可,即先压入左孩子,再压入右孩子。除此之外,因为按照这种方法调整得到的遍历顺序为“中-右-左”,正好是后序遍历的反向顺序,因此在获得遍历序列后还需进行逆序操作。
public class postOrderIterator {
List<Integer> res = new LinkedList<>();
public List<Integer> postOrderTraversal(TreeNode root) {
if (root == null) return res;
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()) {
root = stack.pop();
// 头插法
res.add(0, root.val);
// 左孩子压栈
if (root.left != null) stack.push(root.left);
// 右孩子压栈
if (root.right != null) stack.push(root.right);
}
return res;
}
}
2.2 Morris遍历——空间复杂度O(1)
该方法的思路简单说就是,对于每一个结点,找到它左孩子的最右子结点,因为按照正常访问顺序,其左孩子的最有子节点访问完后就应该访问其本身了,因此将其左孩子最右子节点的右指针指向它。基本步骤如下:
- 如果当前结点左孩子为空,说明最左边访问完毕,将其置为其右孩子
- 如果当前结点左孩子不为空,那么开始尝试找到该结点左孩子的最右子节点,建立连接关系
- 如果找到的当前结点的左孩子的最右子节点右指针为空,说明还未建立连接关系,是首次访问当前结点,那么将该最右结点的右指针指向当前结点,然后当前结点向左孩子走一步继续重复所有步骤。
- 如果找到的当前结点的左孩子的最右子节点右指针不为空,说明已建立过连接关系,是第二次访问当前结点,这意味着当前结点的左子树应该已经全部遍历完了,此时应恢复连接关系重新置为空,然后当前结点向右孩子走一步继续重复所有步骤。
该方法虽然保证了O(1)的空间复杂度,但在遍历过程中改变了部分结点的指向,破坏了树的结构。
2.2.1 中序遍历
public class inOrderMorris {
List<Integer> res = new ArrayList<>();
public List<Integer> inOrderTraversal(TreeNode root) {
TreeNode pre = null;
TreeNode cur = root;
while (cur != null) {
if (cur.left == null) {
res.add(cur.val);
cur = cur.right;
} else {
pre = cur.left;
while (pre.right != null && pre.right != cur) pre = pre.right;
if (pre.right == null) {
pre.right = cur;
cur = cur.left;
} else {
res.add(cur.val);
pre.right = null;
cur = cur.right;
}
}
}
return res;
}
}
2.2.2 前序遍历
public class preOrderMorris {
List<Integer> res = new ArrayList<>();
public List<Integer> preOrderTraversal(TreeNode root) {
TreeNode pre = null;
TreeNode cur = root;
while (cur != null) {
if (cur.left == null) {
res.add(cur.val);
cur = cur.right;
} else {
pre = cur.left;
while (pre.right != null && pre.right != cur) pre = pre.right;
if (pre.right == null) {
res.add(cur.val);
pre.right = cur;
cur = cur.left;
} else {
pre.right = null;
cur = cur.right;
}
}
}
return res;
}
}
2.2.3 后序遍历
前序遍历反向的思想
public class postOrderMorris {
List<Integer> res = new LinkedList<>();
public List<Integer> postOrderTraversal(TreeNode root) {
TreeNode pre = null;
TreeNode cur = root;
while (cur != null) {
if (cur.right == null) {
res.add(0, cur.val);
cur = cur.left;
} else {
pre = cur.right;
while (pre.left != null && pre.left != cur) pre = pre.left;
if (pre.left == null) {
res.add(0, cur.val);
pre.left = cur;
cur = cur.right;
} else {
pre.left = null;
cur = cur.left;
}
}
}
return res;
}
}
基于Java的二叉树的三种遍历方式的递归与非递归实现的更多相关文章
- C++编程练习(8)----“二叉树的建立以及二叉树的三种遍历方式“(前序遍历、中序遍历、后续遍历)
树 利用顺序存储和链式存储的特点,可以实现树的存储结构的表示,具体表示法有很多种. 1)双亲表示法:在每个结点中,附设一个指示器指示其双亲结点在数组中的位置. 2)孩子表示法:把每个结点的孩子排列起来 ...
- 二叉树及其三种遍历方式的实现(基于Java)
二叉树概念: 二叉树是每个节点的度均不超过2的有序树,因此二叉树中每个节点的孩子只能是0,1或者2个,并且每个孩子都有左右之分. 位于左边的孩子称为左孩子,位于右边的孩子成为右孩子:以左孩子为根节点的 ...
- Java中List集合的三种遍历方式(全网最详)
List集合在Java日常开发中是必不可少的,只要懂得运用各种各样的方法就可以大大提高我们开发的效率,适当活用各种方法才会使我们开发事半功倍. 我总结了三种List集合的遍历方式,下面一一来介绍. 首 ...
- PTA 二叉树的三种遍历(先序、中序和后序)
6-5 二叉树的三种遍历(先序.中序和后序) (6 分) 本题要求实现给定的二叉树的三种遍历. 函数接口定义: void Preorder(BiTree T); void Inorder(BiTr ...
- Map三种遍历方式
Map三种遍历方式 package decorator; import java.util.Collection; import java.util.HashMap; import java.util ...
- set的三种遍历方式-----不能用for循环遍历(无序)
set的三种遍历方式,set遍历元素 list 遍历元素 http://blog.csdn.net/sunrainamazing/article/details/71577662 set遍历元素 ht ...
- for 、foreach 、iterator 三种遍历方式的比较
习惯用法 for.foreach循环.iterator迭代器都是我们常用的一种遍历方式,你可以用它来遍历任何东西:包括数组.集合等 for 惯用法: List<String> list = ...
- 大数据学习day13------第三阶段----scala01-----函数式编程。scala以及IDEA的安装,变量的定义,条件表达式,for循环(守卫模式,推导式,可变参数以及三种遍历方式),方法定义,数组以及集合(可变和非可变),数组中常用的方法
具体见第三阶段scala-day01中的文档(scala编程基础---基础语法) 1. 函数式编程(https://www.cnblogs.com/wchukai/p/5651185.html): ...
- Java中Map的三种遍历方法
Map的三种遍历方法: 1. 使用keySet遍历,while循环: 2. 使用entrySet遍历,while循环: 3. 使用for循环遍历. 告诉您们一个小秘密: (下↓面是测试代码,最爱看 ...
随机推荐
- 关于调用三方平台接口与推送接口的总结<二>(2020.7.27)
前言:本篇博客是接着上篇总结写的,想了解怎么对接第三方平台接口的同学可以看我上一篇博客,地址是 https://www.cnblogs.com/alanturingson/p/13377500.ht ...
- PHP strspn() 函数
实例 返回在字符串 "Hello world!" 中包含字符 "kHlleo" 的数目: <?php高佣联盟 www.cgewang.comecho st ...
- intel:spectre&Meltdown侧信道攻击(二)
上面一篇介绍了spectre&meltdown基本原理和简单的demo方案,今天继续学习一下该漏洞发现团队原始的POC:https://spectreattack.com/spectre.pd ...
- RabbitMQ学习总结(3)-集成SpringBoot
1. pom.xml引用依赖 SpringBoot版本可以自由选择,我使用的是2.1.6.RELEASE,使用starter-web是因为要使用Spring的相关注解,所以要同时加上. <dep ...
- 动态绑定CheckBoxList,并默认勾选多选框
首先这是界面展示列: 当我点击更新操作后,效果如下: 其中所属区域的复选框为动态绑定,并且已为我们默认勾选了相关选项,具体操作如下: 前台代码: <tr> <td class=&qu ...
- 微信小程序后台springboot+mybatis+mysql“采坑”集锦
"采坑"错误集锦 1.service层 错误描述:2019-04-14 22:09:52.027 ERROR 8416 --- [nio-8082-exec-5] o.a.c.c. ...
- Pytorch_第九篇_神经网络中常用的激活函数
神经网络中常用的激活函数 Introduce 理论上神经网络能够拟合任意线性函数,其中主要的一个因素是使用了非线性激活函数(因为如果每一层都是线性变换,那有啥用啊,始终能够拟合的都是线性函数啊).本文 ...
- git使用-克隆仓库
1.git clone 克隆地址 克隆地址: 2.克隆命令
- 2020重新出发,JAVA入门,关键字&保留字
关键字 & 保留字 关键字(或者保留字)是对编译器有特殊意义的固定单词,不能在程序中做其他目的使用. 关键字具有专门的意义和用途,和自定义的标识符不同,不能当作一般的标识符来使用.例如, cl ...
- mapper.xml文件中传入list参数报错 ‘ ’附近有语法错误
mapper.xml文件中传入list参数,使用foreach循环遍历值,但是在遍历的过程中出错了,具体代码如下所示 mapper.xml <select id="selectByCo ...