用递归的思想实现二叉树前、中、后序迭代遍历
时间:2022-07-26
本文章向大家介绍用递归的思想实现二叉树前、中、后序迭代遍历,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
先复习一下前、中、后遍历的顺序:
- 前序遍历顺序:中-左-右
- 中序遍历顺序:左-中-右
- 后序遍历顺序:左-右-中
用递归来写二叉树遍历是非常简单的,例如前序遍历的代码如下:
const result = []
function preorderTraversal(node) {
if (!node) return null
result.push(node.val)
preorderTraversal(node.left)
preorderTraversal(node.right)
}
preorderTraversal(root)
我们都知道,在调用函数时,系统会在栈中为每个函数维护相应的变量(参数、局部变量、返回地址等等)。
例如有 a,b,c
三个函数,先调用 a,a 又调用 b,b 最后调用 c。此时的调用栈如图所示:
为什么要说这个呢?因为递归遍历的执行过程就是这样的,只不过是函数不停的调用自身,直到遇到递归出口(终止条件)。
举个例子,现在要用递归前序遍历以下二叉树:
1
2
/
3
它的遍历顺序为 1-2-3
,调用栈如图所示:
理解了递归调用栈的情况,再来看看怎么利用递归思想实现前序迭代遍历:
function preorderTraversal(root) {
const result = []
// 用一个数组 stack 模拟调用栈
const stack = []
let node = root
while (stack.length || node) {
// 递归遍历节点的左子树,直到空为止
while (node) {
result.push(node.val)
stack.push(node)
node = node.left
}
// 跳出循环时 node 为空,由于前序遍历的特性
// 当前 node 节点的上一个节点必定是它的父亲节点
// 前序遍历是中-左-右,现在左子树已经到头了,该遍历父节点的右子树了
// 所以要弹出父节点,从它的右子树开始新一轮循环
node = stack.pop()
node = node.right
}
return result
}
再看一个具体的示例,用迭代遍历跑一遍:
1
/
2 3
/ /
4 5 6 7
- 初始节点 node 为 1
- while 遍历完它的左子节点
- 当前
stack == [1,2,4]
- 初始节点已经遍历完它下面的最后一个左子节点了,即节点 4 的左子节点,所以现在要开始遍历 4 的右子节点
- 弹出节点 4 并从它的右子节点开始新的循环
- 由于节点 4 的右子节点为空,所以不会进入 while 循环,然后弹出节点 4 的父节点 2
- 再从节点 2 的右子节点开始循环
看到这是不是已经发现了这个迭代遍历的过程和递归遍历的过程一模一样?
而且用递归的思想来实现迭代遍历,优点在于好理解,以后再遇到这种问题马上就能想起来怎么做了。
中序遍历
中序遍历和前序遍历差不多,区别在于节点出栈时,才将节点的值推入到 result 中。
function inorderTraversal(root) {
const result = []
const stack = []
let node = root
while (stack.length || node) {
while (node) {
stack.push(node)
node = node.left
}
node = stack.pop()
result.push(node.val)
node = node.right
}
return result
}
后序遍历
前序遍历过程为中-左-右,逆前序遍历过程就是将遍历左右子树的顺序调换一下,即中-右-左。
然后再看一下后序遍历的过程左-右-中,可以看出逆前序遍历顺序的倒序就是后序遍历的顺序。
利用这一特点写出的后序遍历代码如下:
function postorderTraversal(root) {
const result = []
const stack = []
let node = root
while (stack.length || node) {
while (node) {
result.push(node.val)
stack.push(node)
node = node.right // 原来是 node.left,这里换成 node.right
}
node = stack.pop()
node = node.left // 原来是 node.right,这里换成 node.left
}
return result.reverse() // 逆前序遍历顺序的倒序就是后序遍历的顺序
}
参考资料
- 使用shell进行日志分析(r2第14天)
- easyui表单提交验证form
- 数据紧急修复之启用错误日志 (r2第12天)
- javascript 模拟按键点击提交
- 微信小程序调用接口返回数据或提交数据
- 巧用shell脚本生成快捷脚本(r2第12天)
- asp.net动态增加服务器端控件并提交表单
- c# asp.net 实现分页(pager)功能
- 一次数据库无法登陆的"问题"及排查(r2第11天)
- popcorn-js视频Video框架简单用法
- 一次数据库响应缓慢的问题排查(r2第9天)
- 通过Ajax方式上传文件(input file),使用FormData进行Ajax请求
- C# 读取指定文件夹下所有文件
- ASP.NET 实现Base64文件流下载PDF
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- CEF -version 3325完整编译教程
- 第09期:有关 MySQL 字符集的乱码问题
- 机器人系统建模与辨识工具箱sympybotic
- 微服务聚合Swagger文档,这波操作是真的香!
- CEF-version2623完整编译教程
- Node 中如何引入一个模块及其细节
- PHP 错误和异常处理(下)
- PHP 错误和异常处理(上)
- 玩转 PhpStorm 系列(一):主题篇
- 如何用云开发打造“万人同屏”高并发实时互动小程序
- 实战丨云开发帮你和「火箭少女」合个影!
- 开源数据闪回工具—binlog2sql介绍
- 盘点前端面试常见的15个TS问题,你能答对吗?
- 『深度思考』对CenterNet的一些思考与质疑·测试对比CenterNet与U版YoloV3速度与精度
- 优秀员工应该具备的11个特质