目录
先中后序公用的数据 tree:
const bt = {
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. 对根节点的右子树进行先序遍历。
// 先序遍历(先根再左后右)——递归版
const preorder = (root) => {
if(!root) {return}
// 先访问根节点的val
console.log(root.val)
// 再递归左子节点
preorder(root.left)
// 再递归右子节点
preorder(root.right)
}
preorder(bt)
// 先序遍历(先根再左后右)——非递归版(栈)
const preorder = (root) => {
if (!root) { return; }
const stack = [root];
while (stack.length) {
// 将根节点push进去,并打印
const n = stack.pop();
console.log(n.val);
// 先将右子树 push 进去,再 push 左子树,这样在 pop 时才能做到
// 先 pop 出来左子树,其次才会是右子树
if (n.right) stack.push(n.right);
if (n.left) stack.push(n.left);
}
};
preorder(bt);
打印结果:
二、二叉树的中序遍历
1. 对根节点的左子树进行中序遍历;
2. 访问根节点;
3. 对根节点的右子树进行中序遍历。
// 中序遍历(先左再根后右)——递归版
const inorder = (root) => {
if (!root) return
// 先递归左子树
inorder(root.left)
// 访问根节点 val
console.log(root.val)
// 后递归右子树
inorder(root.right)
}
inorder(bt)
// 中序遍历(先左再根后右)——非递归版(栈)
const inorder = (root) => {
if (!root) { return; }
// 存放遍历出来的节点
const stack = [];
// 指针,用于指向当前根节点
let p = root;
// 看这个大循环之前,请先看下面的小循环解析
// 大循环解析:第一次进来,stack 栈为空,p 为根节点,进入循环;
// 第二次再进入大循环,此时 stack 栈里存满了整棵树的左子节点(包括根节点)
// 且在循环体内部, 指针 p 被赋值成 栈里最新的一个左子节点的右子节点,如果该左子节点
// 的右子节点不存在,则跳出小循环,直接把 stack 栈中最后一个左子节点 pop 出来,并访问其
// val, 否则就进入小循环,判断其是否含有左子节点,此时进入大循环的上一个子节点的右子节点
// 已经可以看做根节点了
while (stack.length || p) {
// 可以先看当前这个小循环,小循环先把根节点推入栈中,这里可以理解为根节点是一个左节点
// 然后将根节点的左节点赋值为根节点,再进行循环,一遍一遍地将根节点的左子节点推入栈中
// 直至树的左节点全部被推入栈中
while (p) {
stack.push(p);
p = p.left;
}
// 把栈的最新(即末尾,最后push进去的)一项 pop 出来,并打印,
// 此时就是最后一层左节点的值,即先打印左节点,其实也可以把 pop 出来的
// 这个节点,当作一棵新树的根节点
const n = stack.pop();
console.log(n.val);
// 因为整棵树的左节点都遍历完了,所以这棵新树也不存在左节点了,
// 因为如果存在的话,上面的小循环是不会结束的,所以就把这棵新树
// 的右子节点推入到栈中,此时小循环结束,开始了新的一轮大循环
p = n.right;
}
};
inorder(bt);
打印结果:
三、二叉树的后序遍历
1. 对根节点的左子树进行后续遍历
2. 对根节点的右子树进行后续遍历
3. 访问根节点
// 后序遍历(先左再右后根)——递归版
const postorder = (root) => {
// 如果根节点不存在,即为 null 或 undefined 或 0,就直接 return,
if (!root) return
// 然后递归左子节点
postorder(root.left)
// 再递归右子节点
postorder(root.right)
// 最后访问根节点
console.log(root.val)
}
postorder(bt)
// 后序遍历(先左再右后根)——非递归版(栈)
// 后序遍历的顺序与先序遍历相反,那么是不是可以把先序遍历的顺序掉个个
// 先序遍历的顺序是根 -> 左 -> 右,那么栈里存的就会是 右 -> 左 -> 根
// 后序遍历的顺序是左 -> 右 -> 根,那么栈里存的就会是 根 -> 右 -> 左
const postorder = (root) => {
if (!root) { return; }
// 缓存新的栈,用于存放从 stack 中 pop 出来的节点
const outputStack = [];
// 按根、右、左的顺序将节点推入栈中
const stack = [root];
while (stack.length) {
// 当 stack 中是 root 时,先 pop,即先弹出来的就是根节点
// 然后随着下面左子节点和右子节点的顺序入栈,stack 栈内的顺序就是
// 右 -> 左 -> 根
// 又因为后序遍历肯定是左先出,所以在 stack 中存的应该是 左 -> 右 -> 根
// 这样才能保证在新栈中的顺序是 根 -> 右 -> 左
const n = stack.pop();
// 将根节点进行入栈
outputStack.push(n);
// 因为左在右的前面先被存入栈中,所以先把左子节点推入栈中
if (n.left) stack.push(n.left);
// 再把右子节点推入栈中
if (n.right) stack.push(n.right);
}
while(outputStack.length){
// 最后再把新栈中的 根 -> 右 -> 左 的子节点 pop 出来,并访问
const n = outputStack.pop();
console.log(n.val);
}
};
postorder(bt);
打印结果: