二叉树的遍历:前序、中序、后序、层序

遍历二叉树,说白了就是按照某种规则,把树里的每个节点都访问一遍,而且不重复、不遗漏。这就像你走进一个迷宫,每个房间都要走到,但走法不同——有人喜欢先看当前房间,再进左边和右边;有人非要先把左边所有房间看完,才肯看中间。

我个人习惯把遍历理解为“打印顺序”。你想想看,一棵二叉树存了一堆数字,你用什么顺序把它们打印出来?这就是遍历的核心。

四种遍历方式速览

先给个全景图。二叉树遍历分两大类:深度优先(DFS)和广度优先(BFS)。深度优先里又分三种:前序、中序、后序。广度优先就是层序遍历。

核心记忆法:“根”的位置决定名字。根在前就是前序,根在中间就是中序,根在后就是后序。

二叉树遍历体系 二叉树遍历 深度优先 (DFS) 广度优先 (BFS) 前序遍历 中序遍历 后序遍历 层序遍历 递归实现 / 迭代实现(栈/队列)

前序遍历(根左右)

前序遍历的顺序是:根节点 → 左子树 → 右子树。说白了就是“先看自己,再看左边,最后看右边”。

我在项目中遇到过这样一个场景:用前序遍历来序列化二叉树,把树结构存到文件里。因为前序遍历的第一个节点一定是根,反序列化时很好定位。

// 递归实现前序遍历
void preorder(TreeNode* root) {
    if (root == NULL) return;
    
    printf("%d ", root->val);   // 先访问根
    preorder(root->left);       // 再遍历左子树
    preorder(root->right);      // 最后遍历右子树
}

小技巧:递归代码看着简单,但面试官经常让你写非递归版本。非递归前序遍历用栈模拟,先把根入栈,然后循环:弹出节点→访问→右孩子入栈→左孩子入栈(注意顺序,先右后左,因为栈是后进先出)。

中序遍历(左根右)

中序遍历的顺序是:左子树 → 根节点 → 右子树。这个顺序有个特别重要的性质——对于二叉搜索树(BST),中序遍历的结果是递增有序的

我记得有一次做数据库索引优化,需要验证一棵B+树的变体是否正确构建。我直接写了个中序遍历,打印出来一看,果然有序,心里就踏实了。

// 递归实现中序遍历
void inorder(TreeNode* root) {
    if (root == NULL) return;
    
    inorder(root->left);        // 先遍历左子树
    printf("%d ", root->val);   // 再访问根
    inorder(root->right);       // 最后遍历右子树
}

避坑指南:我曾经在非递归中序遍历上栽过跟头。核心逻辑是:一直往左走,把沿途节点压栈,直到NULL;然后弹出栈顶访问,再转向右子树。很多人忘了“转向右子树”这一步,导致死循环。记住:左到底→弹出访问→往右一步。

后序遍历(左右根)

后序遍历的顺序是:左子树 → 右子树 → 根节点。这个顺序在什么场景下有用?删除二叉树的时候。你想想看,要删除一棵树,得先把孩子删了,才能删父亲吧?后序遍历正好满足这个需求。

// 递归实现后序遍历
void postorder(TreeNode* root) {
    if (root == NULL) return;
    
    postorder(root->left);      // 先遍历左子树
    postorder(root->right);     // 再遍历右子树
    printf("%d ", root->val);   // 最后访问根
}

后序遍历的非递归实现是三种里最麻烦的。需要两个栈,或者用一个栈加一个标记变量记录上次访问的节点。嗯,这里要注意:只有当一个节点的左右子树都访问完了,才能访问它自己。

层序遍历(广度优先)

层序遍历就是一层一层地扫过去,从上到下,从左到右。这个用递归不太好写,队列才是它的最佳搭档。

// 层序遍历(使用队列)
void levelorder(TreeNode* root) {
    if (root == NULL) return;
    
    Queue* q = createQueue();
    enqueue(q, root);
    
    while (!isEmpty(q)) {
        TreeNode* cur = dequeue(q);
        printf("%d ", cur->val);
        
        if (cur->left)  enqueue(q, cur->left);
        if (cur->right) enqueue(q, cur->right);
    }
}

层序遍历的变种题:面试常考“之字形遍历”(也叫锯齿形遍历)。其实就是层序遍历加一个标志位,奇数层从左到右,偶数层从右到左。实现时可以用双端队列,或者普通队列加一个反转操作。

四种遍历的复杂度对比

遍历方式 时间复杂度 空间复杂度(递归) 空间复杂度(迭代) 核心数据结构
前序遍历 O(n) O(h) O(h)
中序遍历 O(n) O(h) O(h)
后序遍历 O(n) O(h) O(h) 栈(双栈或标记)
层序遍历 O(n) O(h) O(w) 队列

注:n 是节点数,h 是树高,w 是树的最大宽度。最坏情况下 h = n(链状树),最好情况下 h = log n(平衡树)。

一个例子看懂全部

假设我们有这样一棵二叉树:

        1
       / \
      2   3
     / \   \
    4   5   6

四种遍历的结果分别是:

  • 前序遍历:1 2 4 5 3 6
  • 中序遍历:4 2 5 1 3 6
  • 后序遍历:4 5 2 6 3 1
  • 层序遍历:1 2 3 4 5 6

你仔细看看中序遍历的结果,是不是左边都比1小,右边都比1大?这就是BST的验证原理。

我的个人习惯:刷题时如果遇到“根据两种遍历结果重建二叉树”的题,我一般先看中序遍历。因为中序遍历能告诉你左右子树的边界。前序+中序可以重建,后序+中序也可以重建,但前序+后序不行——除非是满二叉树。

递归 vs 迭代:怎么选?

递归代码简洁,但函数调用有开销,而且深度太大会栈溢出。迭代代码稍复杂,但可控性好。

我建议:日常写算法题用递归,生产环境或处理大数据量时用迭代。曾经我在嵌入式设备上写树遍历,递归深度超过500就崩了,改成迭代后稳稳的。

嗯,四种遍历就讲到这里。你想想看,其实核心就两样东西:递归顺序栈/队列的模拟。把这两个搞明白,二叉树遍历这块就通了。


公众号:蓝海资料掘金营,微信deep3321