二叉树四种遍历的迭代实现
首先是三种深度优先遍历用同样模板的迭代方式来实现。
前序遍历: 中左右
中序遍历: 左中右
后序遍历:左右中
理解迭代算法的重点在于要理解每次获取一个节点的值的时候,是将这个节点看作 中间节点
来获取值的,而不是简单的获取左边值获取右边值获取中间值这种思路。
即获取所有节点值的过程都是先将这个节点看作一个中间节点然后根据遍历的顺序来决定先获取哪一侧的值。
迭代是通过栈实现,栈后进先出,因此将需要先遍历的节点最后入栈,后遍历的节点先入栈。
统一格式的迭代算法就是每次遍历到一个节点处,将它看作一个中间节点,然后根据遍历的顺序来设置其左右子节点和它本身入栈的顺序,将中间节点入栈后,添加一个空节点。
当读取到空节点时就表示下一个是中间节点,需要获取它的值。(因为每个节点在遍历的过程中都会有被看作中间节点的时候,所以会正确获取所有节点的值)
不是空节点时就表示当前遍历方式下还没有轮到获取节点值,即可能需要先遍历左右节点的值。
可以想作有一个指针,每次都指向中间节点,随着遍历顺序移动。
即使是叶子节点,也会被看作成中间节点,根据遍历顺序来将它的左右节点入栈,并添加空节点。因为不存在左右节点,所以最终会将这个叶子节点和一个空节点入栈,然后出栈读取到空节点,就可以获取这个叶子节点的值了。
前序遍历
前序遍历的顺序是中左右
所以入栈顺序是右左中
当读取到在中节点前的空节点时,获取它的值
代码:
var postorderTraversal = function(root) {
if (!root) return [];
const stack = [];
let res = [];
stack.push(root);
while(stack.length){
node = stack.pop();
// 不是空节点
if (node) {
if (node.right) stack.push(node.right);
if (node.left) stack.push(node.left);
stack.push(node);
stack.push(null);
}
// 空节点表示获取这个中间节点的值
else {
node = stack.pop();
res.push(node.val);
}
}
return res;
};
中序遍历
中序遍历的顺序:左中右
入栈顺序:右中左
同样在添加中节点后,添加一个空节点。当读取到空节点时就表示左子树已经遍历完毕,根据遍历顺序获取中间节点值后遍历右子树。
代码:
var postorderTraversal = function(root) {
if (!root) return [];
const stack = [];
let res = [];
stack.push(root);
while(stack.length){
node = stack.pop();
// 不是空节点
if (node) {
if (node.right) stack.push(node.right);
stack.push(node);
stack.push(null);
if (node.left) stack.push(node.left);
}
// 空节点表示获取这个中间节点的值
else {
node = stack.pop();
res.push(node.val);
}
}
return res;
};
后序遍历
后序遍历的顺序:左右中
所以入栈顺序:中右左
同样在添加中节点后,添加一个空节点。当读取到空节点时就表示左右子树都已经遍历完毕,根据遍历顺序获取中间节点值后获取堆栈中存储的父节点的值。
代码:
var postorderTraversal = function(root) {
if (!root) return [];
const stack = [];
let res = [];
stack.push(root);
while(stack.length){
node = stack.pop();
// 不是空节点
if (node) {
stack.push(node);
stack.push(null);
if (node.right) stack.push(node.right);
if (node.left) stack.push(node.left);
}
// 空节点表示获取这个中间节点的值
else {
node = stack.pop();
res.push(node.val);
}
}
return res;
};
层序遍历:
上面三种方式都是深度优先遍历,得先把左子树遍历完才会遍历右子树。
所以要利用栈的先进后出特性,递归也是函数栈(先一直遍历左侧再遍历右侧)。
利用后进先出,一直添加并弹出末尾元素来实现对左子树一直深度优先访问。
而层序遍历是广度优先遍历,通过队列先进先出实现。即同一层节点永远会比下一层节点先输出。
实现代码:
var levelOrder = function(root) {
// 队列先进先出实现 一个节点出队时将其左右节点放入队尾
if (!root) return []
const queue = [];
let res = [], level_res;
queue.push(root)
while(queue.length){
// 统计当层节点数量, 因为queue长度是动态的,所以要提前记录好
level_size = queue.length;
// 存放当层所有节点值
level_res = [];
for(let i=0; i<level_size; i++){
node = queue.shift();
level_res.push(node.val);
// 队列先进先出
if (node.left) queue.push(node.left);
if (node.right) queue.push(node.right);
}
res.push(level_res);
}
return res;
};