二叉树的遍历方式
二叉树的遍历方式有四种,分别是前序遍历,中序遍历,后序遍历和层级遍历。
其中前序、中序、后序遍历呢,又可以细分为递归遍历,基于栈的遍历和Morris遍历。层级遍历主要是基于栈的遍历。
下面我们来分别讲讲这几种遍历方式。
递归遍历
递归遍历非常的简单,直接记住套路,直接套即可。
function traversal(root) {
if(!root) return;
// 前序
traversal(root.left);
// 中序
travesal(root.right);
// 后序
}
由二叉树的前中后序遍历的过程可知,前序遍历是 根左右,中序遍历是 左根右,后序遍历是 左右根。
你看,刚好对上了上面的代码,上面代码的注释部分就是处理我们当前的节点信息的,也就是 根 的信息。
基于栈的遍历
前序遍历
前序遍历是先根后左右,但由于栈的特性,必然是先处理根,然后分别入栈右节点,再入栈左节点。
那么处理节点时自然是先处理左节点后处理右节点了。
funtion preorderTraversal(root) {
let res = [];
let stack = [];
let node = root;
while (node) {
res.push(node.val);
if (node.right) {
stack.push(node.right);
}
node = node.left;
if (!node) {
node = stack.pop();
}
}
return res;
}
中序遍历
在前序、后序遍历中,while
只需要判断当前节点情况就好了,但是中序遍历还需加多一个栈是否为空的判断。
为什么要加多一个栈的判断呢,我在 while 里加多一个判断,只要 node
节点为空了,我就取栈顶元素赋值给它不行吗?
如果这么操作,我们就会进入无限的死循环中了。因为中序遍历是从最左下节点开始的,所以对于一个子树我们需要跑到它最左下节点开始操作。
就比如你处理了一颗子树的最左下节点了,在 while 里我们又把 node
赋值到当前节点的父节点了,其必是又要跑到最左下节点开始操作的,那么死循环开始了。
function inorderTraversal(root) {
let res = [];
let stack = [];
let node = root;
while (node || stack.length) {
while (node) {
stack.push(node);
node = node.left;
}
node = stack.pop();
res.push(node.val);
node = node.right;
}
return res;
}
后序遍历
后序遍历是左右根,也是因为栈的特性,把节点的子节点入栈时,需要注意一点点,先入栈左节点,后入栈右节点。
处理元素时,先处理当前根节点,然后处理右节点,再处理左节点。此时我们处理的元素倒转一下就是后序遍历的结果了。
(当然也可以直接调用Javascript提供的数组方法unshift,那就不要倒转结果集了。)
function postorderTraversal(root) {
let res = [];
let stack = [];
let node = root;
while (node) {
if (node.left) {
stack.push(node.left);
}
if (node.right) {
stack.push(node.right);
}
res.push(node.val);
node = stack.pop();
}
res = res.reverse();
return res;
}
前、中、后序遍历,我们的 while 循环都是要有一个共同的判断 判断当前节点,而中序遍历呢需要加多一个栈是否为空的判断。
层级遍历
层级遍历就更简单了,就是从根到叶子结点,从左到右的遍历每一层。话不多说,直接上代码。
function levelTraversal(root) {
if (!root) return [];
let res = [];
let stack = [root];
while (stack.length) {
let node = stack.shift();
res.push(node.val);
if (node.left) {
stack.push(node.left);
}
if (node.right) {
stack.push(node.right);
}
}
return res;
}