这是我参与更文挑战的第12天,活动详情查看:更文挑战
常见的二叉树遍历方式主要分为前序、中序、后序遍历和层序遍历。以下图这棵二叉树进行简单讲解。
层序遍历很容易理解,就是一层层地来,像上图这棵树,进行层序遍历的结果就是 【1,【2,3】,【4,5,6,7】】。
而前序、中序和后序遍历中的「前」、「中」、「后」指的是二叉树的根节点被取值的先后,节点左右两边节点则是严格按照先左后右的顺序。
以上四种二叉树遍历方式分别都有两种实现的思路:递归及非递归。
层序遍历
递归思路的层序遍历实现起来比较简单,如下所示:
// 将树的每一层都单独一个数组项,比如上图的二叉树实现层序遍历的结果:
// [[1], [2,3], [4,5,6,7]]
// 树节点是一个对象,结构如下所示:
// {
// val: ...,
// left: ...,
// right: ...
// }
var levelOrder = function(root) {
// 临界条件判断
if (!root) return [];
let result = [];
// level 记录当前层级
function traversal(node, level) {
if (!result[level]) result[level] = [];
// 将节点放入当前层
result[level].push(node.val);
// 递归,将左节点放入下一层
node.left && traversal(node.left, level+1);
// 递归,将右节点放入下一层
node.right && traversal(node.right, level+1);
}
traversal(root, 0);
return result;
};
除了递归解法,也可以利用队列,通过循环的方式进行实现:
var levelOrder = function(root) {
if (!root) return [];
let result = [];
let queue = [root];
while(queue.length) {
let arr = [];
// 当前队列的节点数就是当前层的节点数
let len = queue.length;
while(len) {
// 取出队列节点,放入当前层的数组arr中
let node = queue.shift();
arr.push(node.val);
// 如果有左右孩子节点,就进入到队列后边
node.left && queue.push(node.left);
node.right && queue.push(node.right);
len--;
}
result.push(arr);
}
return result;
};
LeetCode 102题是关于二叉树层序遍历的题目,可以自行尝试一下~。
前、中、后序遍历
拿上文示例的二叉树来说,前序遍历按照「中左右」顺序从根节点开始,遍历结果为 【1,1节点左边子树,1节点右边子树】。对于 1 节点左边的子树,进行「中左右」顺序遍历的结果是 【2,4,5】,对于 1 节点右边的子树,进行「中左右」顺序遍历的结果是【3,6,7】,最终得到的结果为 【1,2,4,5,3,6,7】。
中序遍历则是按照「左中右」顺序从根节点开始,遍历结果为【1节点左边的子树,1,1 节点右边的子树】,对于 1 节点左边的子树,进行「左中右」顺序遍历的结果是 【4,2,5】,对于 1 节点右边的子树,进行「左中右」顺序遍历的结果是【6,3,7】,最终得到的结果为 【4,2,5,1,6,3,7】。
后序遍历就不多说了,遍历结果为 【4,5,2,6,7,3,1】。
我们先来看中序遍历的递归解法:
var inorderTraversal = function(root) {
if (!root) return [];
let arr = [];
function traversal(node) {
// 先对左边的节点进行递归深度遍历
if (node.left) traversal(node.left);
// 根节点
arr.push(node.val)
// 对右边的节点进行递归深度遍历
if (node.right) traversal(node.right);
};
traversal(root);
return arr;
};
通过函数 traversal 的递归调用实现了遍历。前序和后序遍历只是根节点的顺序不一样,那我们完全可以复用中序遍历的代码,将递归函数内的代码顺序调换一下即可。
前序遍历和后序遍历的 traversal 函数如下所示:
//... 前序遍历
function traversal(node) {
arr.push(node.val)
if (node.left) traversal(node.left);
if (node.right) traversal(node.right);
};
//... 后序遍历
function traversal(node) {
if (node.left) traversal(node.left);
if (node.right) traversal(node.right);
arr.push(node.val)
};
非递归解法,我们同样还是以中序遍历为例,利用栈结构,通过循环实现:
var inorderTraversal = function(root) {
if (!root) return [];
let result = [];
let stack = [root];
let node;
while(stack.length) {
// 让顶部的节点出栈进行逻辑判断
node = stack.pop();
if (node) {
// 因为栈是后进先出,所以右边的子节点先进栈,压在栈底
node.right && stack.push(node.right);
// 把根节点重新压到栈内,同时再压入一个null值,做标记
stack.push(node, null);
// 左边的节点进栈
node.left && stack.push(node.left);
} else { // null 值,意味着下一个是遍历完子节点的根节点
result.push(stack.pop().val);
}
}
return result;
};
同样的,前序遍历和后序遍历也是在这道题的基础上经过小幅改动就可以复用。对 while 循环中的代码进行改动,实现如下:
// ... 前序遍历
// 根节点不需要重新入栈,所以不需要 null 值进行判断
while(stack.length) {
let node = stack.pop();
result.push(node.val);
node.right && stack.push(node.right);
node.left && stack.push(node.left);
}
// ...后序遍历
// 根节点不需要重新入栈,所以不需要 null 值进行判断
while(stack.length) {
let node = stack.pop();
if (node) {
stack.push(node, null);
node.right && stack.push(node.right);
node.left && stack.push(node.left);
} else {
result.push(stack.pop().val);
}
}
LeetCode 的第 94、144、145题分别是二叉树的中序、前序和后序遍历题,不妨试试看~
最后
以上就是与二叉树遍历相关知识分享,如果文章对你有帮助,点个赞呗~