注意:以下遍历的数据都为下方代码段中的二叉树结构
const tree = {
val: 1,
left: {
val: 2,
left: {
val: 4,
left: null,
right: null,
},
right: {
val: 5,
left: null,
right: null,
},
},
right: {
val: 3,
left: {
val: 6,
left: null,
right: null,
},
right: {
val: 7,
left: null,
right: null,
},
},
};
一:先序遍历的非递归写法
思路:递归遍历本质是递归函数不断调用自身,在不断调用自身的过程会产生一个递归堆栈,所以非递归的核心思想就是用一个栈模拟递归时产生的递归堆栈。
1.先序遍历的输出顺序为:根左右,想要第一次就输出根节点的值就需要在创建栈时先传入根节点,且每向下遍历一个新节点时都会先输出这个节点的值。
2.循环条件为栈不为空
3.因为每向下遍历一个新节点时都会先输出这个节点的值,所以每次循环都要先出栈一个元素并访问它的值,然后向栈中添加出栈节点的左右子树。
4.因为栈是后进先出,所以为了实现根左右的遍历顺序,要先将右节点入栈,再入栈左节点。
// 先序遍历---递归版
const preorder1 = (root) => {
if (!root) { return; }
console.log(root.val);
preorder1(root.left);
preorder1(root.right);
};
// 先序遍历---非递归版(结合栈)
// 递归的本质其实就是形成了一个函数调用堆栈,所以用栈模拟原递归方法
const preorder2 = (root) => {
// 特殊情况处理
if (!root) { return; }
// 建立一个栈,并把根节点存入进去------为什么先序可以上来把根节点先存进去
const stack = [root];
// 栈为空表示遍历结束
while (stack.length) {
// 因为先序遍历,所以先访问根节点的值
// 方法就是出栈
const n = stack.pop();
console.log(n.val);
// 先序遍历,输出顺序为跟左右
// 结合栈后进先出的特点,所以左节点要后入栈,右节点要先入栈
if (n.right) stack.push(n.right);
if (n.left) stack.push(n.left);
}
};
二:中序遍历非递归写法
思路:
栈 + 指针
还是用栈模拟递归调用产生的堆栈实现非递归写法,由于中序遍历的输出顺序为左根右,并非一上来就输出节点,所以创建栈时不需要传入根节点。
0.循环条件为:栈不为空且指针存在
1.为了优先遍历完根节点的左子树,所以需要配合指针,让指针不断指向根节点的左子树且将左子树节点不断推入栈中直到指针为空。
2.当左子树遍历到空了,意味着左根右的左完成了该到根了,也就是该输出了。
3.输出就是出栈当前节点,访问该节点的值,然后该根左右的右了,所以让指针指向该出栈节点的右子节点。
4.循环结束即完成了二叉树的中序遍历。
const bt = require('./bt');
// 中序遍历---递归版
const inorder1 = (root) => {
if (!root) { return; }
inorder1(root.left);
console.log(root.val);
inorder1(root.right);
};
// 中序遍历---非递归版
const inorder2 = (root) => {
if (!root) { return; }
// 为啥中序就需要一个空栈
const stack = [];
let p = root;
while (stack.length || p) {
// 因为中序遍历,左根右的顺序
// 所以只要根节点有左节点就一直入栈
if(p) {
stack.push(p);
p = p.left;
} else {
// 根节点没有左节点了,开始入栈右节点
// 输出根节点在右节点入栈之前
let node = stack.pop();
console.log(node.val);
p = node.right;
}
}
};
三:后序遍历非递归写法
思路:
利用伪先序遍历结合栈实现,额外创建一个栈存储伪先序遍历的遍历顺序节点,然后再依次出栈这个栈,即可实现后续遍历。
伪先序遍历就是 输出顺序为 根右左 的先序遍历,以这种顺序入栈,再出栈的时候由于后进先出就变成了 左右根 的顺序,即后序遍历顺序。
const postorder1 = (root) => {
if (!root) { return; }
postorder1(root.left);
postorder1(root.right);
console.log(root.val);
};
// 后序遍历---非递归操作
// 思路:
// 先用类似先序遍历的方式,按 根右左 的顺序将二叉树存入到一个栈中
// 利用栈先进后出的特性,将这个栈逐个出栈,就实现了 左右根 的遍历了
// 步骤:
// 1.类似先序遍历的写法,以根右左的顺序存入栈中
// 2.依次出栈
const postorder2 = (root) => {
if (!root) { return; }
// 接收以 根右左 先序遍历顺序的栈
const outputStack = [];
// 使原二叉树以 根右左 顺序先序遍历
const stack = [root];
while (stack.length) {
const n = stack.pop();
outputStack.push(n);
// 由于栈后进先出且遍历顺序为根右左,所以右要后入栈才能先被遍历出来
if (n.left) stack.push(n.left);
if (n.right) stack.push(n.right);
}
while(outputStack.length){
const n = outputStack.pop();
console.log(n.val);
}
};