前言
二叉树是什么
二叉树是另一种树型结构,它的特点是每个结点至多只有两棵子树(即二叉树中不存在度大于2的结点),并且,二叉树的子树有左右之分(其次序不能任意颠倒。)
二叉树的遍历方式
二叉树的遍历方式有三种主要的类型:前序遍历、中序遍历、后序遍历。这三种遍历方式是基于树的深度优先搜索(DFS)的。其次还有层次遍历,也称为广度优先搜索(BFS)。下面我将为大家介绍一下这四种遍历方式。
下列示例统一用如下二叉树的解构:
A
/ \
B C
/ \ \
D E F
前序遍历
前序遍历的顺序是:根节点 -> 左子树 -> 右子树。
示例
let root = {
val: 'A',
left: {
val: 'B',
left: {
val: 'D',
},
right: {
val: 'E',
}
},
right: {
val: 'C',
right: {
val: 'F',
}
}
};
function preOrder(root) {
if (!root) return ;
// 存储遍历结果的数组
let res = [];
// 将当前根节点的值添加到结果数组
res.push(root.val);
// 递归遍历左子树
preOrder(root.left);
// 递归遍历右子树
preOrder(root.right);
// 返回最终的遍历结果数组
return res;
}
// 调用 preOrder 函数,并输出结果
console.log(preOrder(root));
如上述代码所示,我们通过构造一个preOrder的函数,然后通过递归的方式,遍历左子树和右子树,最后返回结果。总的来说前序遍历的步骤如下:
前序遍历的步骤:
- 访问根节点(Root): 首先访问二叉树的根节点,并将根节点的值记录下来。
- 递归遍历左子树: 对根节点的左子树进行前序遍历,重复上述步骤。
- 递归遍历右子树: 对根节点的右子树进行前序遍历,重复上述步骤。
中序遍历
中序遍历的顺序为:左子树 -> 根节点 -> 右子树
示例
function midOrder(root) {
if (!root) return; // 如果节点为空,直接返回
// 递归遍历左子树
midOrder(root.left);
// 输出当前节点的值
console.log(root.val);
// 递归遍历右子树
midOrder(root.right);
}
midOrder(tree);
上述代码中与前序遍历函数的区别在于输出节点值的位置,中序遍历将输出操作放在左子树递归之后、右子树递归之前,从而实现中序遍历的顺序。
中序遍历的步骤:
- 递归遍历左子树: 对根节点的左子树进行中序遍历,重复下面的步骤。
- 访问根节点: 访问当前根节点的值。
- 递归遍历右子树: 对根节点的右子树进行中序遍历,重复下面的步骤。
后序遍历
后序遍历的顺序为:左子树 -> 右子树 -> 根节点
示例
function backOrder(root) {
if (!root) return []; // 如果节点为空,直接返回空数组
const result = [];
// 递归遍历左子树
result.push(...postOrderTraversal(root.left));
// 递归遍历右子树
result.push(...postOrderTraversal(root.right));
// 访问根节点
result.push(root.val);
return result;
}
backOrder(tree)
上述代码与中序遍历函数的区别在于访问根节点的位置,后序遍历将访问操作放在左子树递归之后、右子树递归之后,从而实现后序遍历的顺序。
后序遍历的步骤:
- 递归遍历左子树: 对根节点的左子树进行后序遍历,重复下面的步骤。
- 递归遍历右子树: 对根节点的右子树进行后序遍历,重复下面的步骤。
- 访问根节点: 访问当前根节点的值。
层次遍历
层次遍历按照树的层级一层一层地进行,从上到下、从左到右遍历。在层次遍历中,同一层的节点会按照从左到右的顺序访问。
示例
// 定义二叉树广度优先搜索函数
function BFS(root) {
// 创建一个空队列,用于存储待访问的节点
const queue = [];
// 将根节点推入队列
queue.push(root);
// 循环遍历,直到队列为空
while (queue.length) {
// 取出队首元素,即当前要访问的节点
const top = queue[0];
// 输出当前节点的值
console.log(top);
// 如果当前节点有左子节点,将左子节点推入队列
if (top.left) {
queue.push(top.left);
}
// 如果当前节点有右子节点,将右子节点推入队列
if (top.right) {
queue.push(top.right);
}
// 出队操作,将队首元素移出队列
queue.shift();
}
}
// 调用广度优先搜索函数
BFS(root);
上述代码使用队列来实现层次遍历。首先将根节点入队,然后循环遍历队列,依次取出队首元素,输出节点值,将其子节点入队,直到队列为空。最终返回按照层次遍历的顺序存储的结果数组。
层次遍历的步骤:
-
初始化队列: 创建一个空队列,用于存储待访问的节点。
-
根节点入队: 将根节点入队。
-
循环遍历: 使用循环,只要队列不为空,就一直执行循环体。
- 取出队首元素: 在循环体中,取出队列的第一个元素,即当前要访问的节点。
- 输出节点值: 输出当前节点的值,进行其他操作。
- 子节点入队: 如果当前节点有左子节点,将左子节点入队;如果有右子节点,将右子节点入队。
- 出队操作: 将队首元素出队。
-
遍历结束: 当队列为空时,遍历结束。
对比前三种方式来说,层次遍历与其他遍历方式(例如前序遍历、中序遍历、后序遍历)的主要区别在于遍历的顺序和访问节点的时机。
区别
-
遍历顺序:
- 层次遍历: 按照树的层次顺序逐层访问节点。首先访问根节点,然后是第一层的所有节点,接着是第二层的所有节点,以此类推。
- 前序遍历: 根节点 -> 左子树 -> 右子树。首先访问根节点,然后是左子树,最后是右子树。
- 中序遍历: 左子树 -> 根节点 -> 右子树。首先访问左子树,然后是根节点,最后是右子树。
- 后序遍历: 左子树 -> 右子树 -> 根节点。首先访问左子树,然后是右子树,最后是根节点。
-
访问节点的时机:
- 层次遍历: 在取出队首元素的时候即可访问节点。
- 前序遍历: 在递归遍历左子树之前访问节点。
- 中序遍历: 在递归遍历左子树之后、右子树之前访问节点。
- 后序遍历: 在递归遍历左子树和右子树之后访问节点。
总结
遍历方式总结:
-
层次遍历:
- 顺序: 按照树的层次逐层访问。
- 数据结构: 使用队列作为辅助数据结构。
- 实现方式: 广度优先搜索,先访问根节点,然后逐层访问每个节点。
-
前序遍历:
- 顺序: 根节点 -> 左子树 -> 右子树。
- 数据结构: 通常使用递归实现,也可使用栈作为辅助数据结构。
- 实现方式: 深度优先搜索,在递归遍历左子树之前访问节点。
-
中序遍历:
- 顺序: 左子树 -> 根节点 -> 右子树。
- 数据结构: 通常使用递归实现,也可使用栈作为辅助数据结构。
- 实现方式: 深度优先搜索,在递归遍历左子树之后、右子树之前访问节点。
-
后序遍历:
- 顺序: 左子树 -> 右子树 -> 根节点。
- 数据结构: 通常使用递归实现,也可使用栈作为辅助数据结构。
- 实现方式: 深度优先搜索,在递归遍历左子树和右子树之后访问节点。