以一定的顺序规则,逐个访问二叉树的所有结点,这个过程就是二叉树的遍历。按照顺序规则的不同,遍历方式有以下四种:
- 先序遍历
- 中序遍历
- 后序遍历
- 层次遍历
按照实现方式的不同,遍历方式又可以分为以下两种:
- 递归遍历(先、中、后序遍历)
- 迭代遍历(层次遍历)
遍历的三种顺序
- 根结点 -> 左子树 -> 右子树
- 左子树 -> 根结点 -> 右子树
- 左子树 -> 右子树 -> 根结点
上述三个遍历顺序,就分别对应了二叉树的先序遍历、中序遍历和后序遍历规则。
在这三种顺序中,根结点的遍历分别被安排在了首要位置、中间位置和最后位置。
所谓的“先序”、“中序”和“后序”,“先”、“中”、“后”其实就是指根结点的遍历时机。
先序遍历

上面图中二叉树的结构用代码来表示:
const root = {val: "A",left: {val: "B",left: {val: "D"},right: {val: "E"}},right: {val: "C",right: {val: "F"}}};
递归函数的编写要点
编写一个递归函数之前,首先要先明确两个东西:
- 递归式
- 递归边界
递归式,它指的是你每一次重复的内容是什么。在这里,我们要做先序遍历,那么每一次重复的其实就是 根结点 -> 左子树 -> 右子树 这个旅行路线。
递归边界,它指的是你什么时候停下来。
在遍历的场景下,当我们发现遍历的目标树为空的时候,就意味着旅途已达终点、需要画上句号了。这个“画句号”的方式,在编码实现里对应着一个 return 语句——这就是二叉树遍历的递归边界。
先序遍历的编码实现:
// 所有遍历函数的入参都是树的根结点对象function preorder(root) {// 递归边界,root 为空if(!root) {return}// 输出当前遍历的结点值console.log('当前遍历的结点值是:', root.val)// 递归遍历左子树preorder(root.left)// 递归遍历右子树preorder(root.right)}
中序遍历
递归边界照旧,唯一发生改变的是递归式里调用递归函数的顺序——左子树的访问会优先于根结点。我们参考先序遍历的分析思路,来写中序遍历的代码:
// 所有遍历函数的入参都是树的根结点对象function inorder(root) {// 递归边界,root 为空if(!root) {return}// 递归遍历左子树inorder(root.left)// 输出当前遍历的结点值console.log('当前遍历的结点值是:', root.val)// 递归遍历右子树inorder(root.right)}
后序遍历
在后序遍历中,我们先访问左子树,再访问右子树,最后访问根结点:
function postorder(root) {// 递归边界,root 为空if(!root) {return}// 递归遍历左子树postorder(root.left)// 递归遍历右子树postorder(root.right)// 输出当前遍历的结点值console.log('当前遍历的结点值是:', root.val)}
