前言
二叉树遍历终结篇,用 js 和图描述二叉树的前序遍历、中序遍历、后序遍历,层次遍历以及广度优先遍历(BFS)和深度优先遍历(DFS)
准备
- 为练习准备下图中的二叉树
- 使用 JS 对象,将上图中的树录入到代码
const treeObj = {
val: 1,
left: {
val: 2,
left: {
val: 4,
},
right: {
val: 5,
left: {
val: 7,
},
right: {
val: 8,
},
},
},
right: {
val: 3,
right: {
val: 6,
},
},
}
前序遍历(深度优先遍历)
注
深度优先遍历(DFS)与前序遍历相同,写在一起
图解
递归思路
- 打印当前节点的值
- 递归调用自身,传入左子树
- 递归调用自身,传入右子树
递归代码
const arr = []
function fun(tree) {
if(!tree){
return
}
arr.push(tree.val)
fun(tree.left)
fun(tree.right)
}
fun(treeObj)
console.log(arr)
// 输出:[1, 2, 4, 5, 7, 8, 3, 6]
非递归思路
-
将根节点塞入栈
-
执行以下循环,当栈为空结束循环
-
- 推出栈中的第一个节点
- 检测是否有右子树,如果有,塞入栈
- 检测是否有左子树,如果有,塞入栈
- 打印当前推出节点的值
非递归代码
function fun(tree) {
const result = []
// 根节点塞入栈
const stack = [tree]
let currentNode
while(stack.length > 0){
// 推出栈中的第一个节点
currentNode = stack.pop()
// 如果有右节点,右节点入栈
if(currentNode.right){
stack.push(currentNode.right)
}
// 如果有左节点,左节点入栈
if(currentNode.left){
stack.push(currentNode.left)
}
// 打印当前节点
result.push(currentNode.val)
}
return result
}
中序遍历
图解
递归思路
- 递归调用自身,传入左子树
- 打印当前节点
- 递归调用自身,传入右子树
递归代码
const arr = []
function fun(tree) {
if(!tree){
return
}
fun(tree.left)
arr.push(tree.val)
fun(tree.right)
}
fun(treeObj)
console.log(arr)
// 输出:[4, 2, 7, 5, 8, 1, 3, 6]
非递归思路
- 执行循环,当树为空并且栈为空时停止循环
-
- 从根节点开始将所有的左节点入栈,直到最后一个左节点
- 推出栈中的最后一个节点
- 打印节点值
- 如果推出的节点有右子树,基于右子树继续执行步骤 a
- 如果没有右子树,执行步骤 b,重新推出栈中的最后一个节点
非递归代码
function fun(root) {
if(!root){
return [];
}
var result = []
var stack = []
while(stack.length!==0||root){
// 从树的根节点开始将所有的左节点塞入栈
while(root){
stack.push(root);
root = root.left;
}
// 推出栈中的节点
root = stack.pop();
result.push(root.val)
// 基于右子树继续进行下一次循环
// 右子树是否存在,下次循环中判断
root = root.right;
}
return result;
}
后序遍历
图解
递归思路
- 递归调用自身,传入左子树
- 递归调用自身,传入右子树
- 打印当前节点
递归代码
const arr = []
function fun(tree) {
if(!tree){
return
}
fun(tree.left)
fun(tree.right)
arr.push(tree.val)
}
fun(treeObj)
console.log(arr)
// 输出:[4, 7, 8, 5, 2, 6, 3, 1]
非递归思路
-
刚好与前序遍历相反
-
将根节点塞入栈
-
执行以下循环,当栈为空结束循环
-
- 推出栈中的第一个节点
- 检测是否有左子树,如果有,塞入栈
- 检测是否有右子树,如果有,塞入栈
- 将当前节点从数组首位插入到结果数组
-
打印结果数组
非递归代码
function fun(tree) {
const result = []
// 根节点塞入栈
const stack = [tree]
let currentNode
while(stack.length > 0){
// 推出栈中的第一个节点
currentNode = stack.pop()
// 如果有左节点,左节点入栈
if(currentNode.left){
stack.push(currentNode.left)
}
// 如果有右节点,右节点入栈
if(currentNode.right){
stack.push(currentNode.right)
}
// 打印当前节点
result.unshift(currentNode.val)
}
return result
}
层序遍历(广度优先遍历)
注
广度优先遍历(BFS)与层序遍历相同,写在一起
实现思路
-
使用队列存储树
-
将树存入队列
-
进行循环迭代,直到队列为空,停止
-
- 从队列首部推出节点
- 打印当前节点
- 如果有左节点,从队列尾部推入左节点
- 如果有右节点,从队列尾部推入右节点
-
打印最终结果
function fun(root){
if(!root){
return [];
}
var queue = [root];
var result = [];
while (queue.length!==0){
// 队列首部推出
var node = queue.shift();
// 记录结果
result.push(node.val);
// 尾部推入左子树
if(node.left){
queue.push(node.left);
}
// 尾部推入右子树
if(node.right){
queue.push(node.right);
}
}
return result;
}
总结
个人感觉二叉树遍历的非递归调用没什么作用,不便于理解,入门可以优先研究递归的调用方式,在层次遍历的实现中也找到了队列的使用场景。