基于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循环遍历. 告诉您们一个小秘密: (下↓面是测试代码,最爱看 ...
随机推荐
- 记一次抓包和破解App接口
目录 第一章 · 起源 第二章 · 尝试 第三章 · 脱狱 第四章 · 柳暗花明 第五章 · 终结 第一章 · 起源 某日,想做个爬虫工具,爬某个网站上的数据已做实验之用.大家都知道爬pc网页上的数据 ...
- nginx location proxy_pass 后面的url 加与不加/的区别
在nginx中配置proxy_pass时,当在后面的url加上了/,相当于是绝对根路径,则nginx不会把location中匹配的路径部分代理走;如果没有/,则会把匹配的路径部分也给代理走. 首先是l ...
- IO流——转换流、缓冲流
一.转换流 1. OutputStreamWriter类 属于字符输出流,OutputStreamWriter 是字符流通向字节流的桥梁:可使用指定的字符编码表,将要写入流中的字符编码成字节. 它的作 ...
- OpenFeign使用步骤
1. 新建 cloud-consumer-feign-order80 2. pom.xml <?xml version="1.0" encoding="UTF-8& ...
- 用xshell连接linux服务器失败 Could not connect to '112.74.73.194' (port 22): Connection failed.
用XSHELL连接linux服务器出现以下错误 Connecting to 42.51.xxx.xxx:22... Connection established. To escape to local ...
- PHP xml_set_default_handler() 函数
定义和用法 xml_set_default_handler() 函数为 XML 解析器建立默认的数据处理器.高佣联盟 www.cgewang.com 该函数规定在只要解析器在 XML 文件中找到数据时 ...
- 怎么下载腾讯课堂M3U8格式的视频
好好学习,天天向上 本文已收录至我的Github仓库DayDayUP:github.com/RobodLee/DayDayUP,欢迎Star,更多文章请前往:目录导航 前言 用过腾讯课堂的小伙伴们可能 ...
- Python环境搭建、python项目以docker镜像方式部署到Linux
Python环境搭建.python项目以docker镜像方式部署到Linux 本文的项目是用Python写的,记录了生成docker镜像,然后整个项目在Linux跑起来的过程: 原文链接:https: ...
- python8.2线程锁
import threading lock=threading.Lock()#创建线程锁:互斥锁num=100def run(name): lock.acquire()#设置锁 global num# ...
- python3.2求和与最值
#1到100求和sum1=0for x in range(101): sum1=sum1+xprint(sum1) #1到100偶数求和sum2=0for x in range(0,101,2): p ...