二叉树遍历:从递归到重建,一次讲透

各位同学,今天我们来啃二叉树遍历这块硬骨头。说实话,我当年学数据结构时,第一次看到前序、中序、后序的递归代码,觉得这玩意儿简直像魔法——几行代码就把整棵树摸得清清楚楚。后来在嵌入式项目中,我经常用二叉树来管理设备状态树,遍历操作几乎天天见。今天我就把压箱底的经验都抖出来。

一、递归遍历:三句话搞定

二叉树的递归遍历,说白了就是三句话的排列组合。你想想看,每个节点都做三件事:访问自己、遍历左子树、遍历右子树。这三件事的顺序一变,就变成了三种遍历方式。

核心口诀:

  • 前序:根 → 左 → 右
  • 中序:左 → 根 → 右
  • 后序:左 → 右 → 根

代码实现更是简单到令人发指。我直接上代码,你们感受一下:

// 二叉树节点定义
typedef struct TreeNode {
    int data;
    struct TreeNode* left;
    struct TreeNode* right;
} TreeNode;

// 前序遍历:根左右
void preOrder(TreeNode* root) {
    if (root == NULL) return;
    printf("%d ", root->data);   // 访问根
    preOrder(root->left);        // 遍历左子树
    preOrder(root->right);       // 遍历右子树
}

// 中序遍历:左根右
void inOrder(TreeNode* root) {
    if (root == NULL) return;
    inOrder(root->left);         // 遍历左子树
    printf("%d ", root->data);   // 访问根
    inOrder(root->right);        // 遍历右子树
}

// 后序遍历:左右根
void postOrder(TreeNode* root) {
    if (root == NULL) return;
    postOrder(root->left);       // 遍历左子树
    postOrder(root->right);      // 遍历右子树
    printf("%d ", root->data);   // 访问根
}

嗯,这里要注意:递归的终止条件一定是 root == NULL。我曾经见过新手写成 root->left == NULL 才返回,结果栈溢出崩了一下午。

二、层序遍历:队列才是王道

递归遍历是深度优先,层序遍历是广度优先。说白了就是一层一层往下扫,像雷达扫描一样。实现层序遍历,队列是标配。为什么?因为队列先进先出的特性,正好能保证同一层的节点按顺序被处理。

我在项目中用层序遍历做过一个设备树的可视化工具,效果拔群。代码长这样:

#include <stdio.h>
#include <stdlib.h>

// 队列节点
typedef struct QueueNode {
    TreeNode* treeNode;
    struct QueueNode* next;
} QueueNode;

// 队列结构
typedef struct {
    QueueNode* front;
    QueueNode* rear;
} Queue;

// 初始化队列
void initQueue(Queue* q) {
    q->front = q->rear = NULL;
}

// 入队
void enqueue(Queue* q, TreeNode* node) {
    QueueNode* newNode = (QueueNode*)malloc(sizeof(QueueNode));
    newNode->treeNode = node;
    newNode->next = NULL;
    if (q->rear == NULL) {
        q->front = q->rear = newNode;
        return;
    }
    q->rear->next = newNode;
    q->rear = newNode;
}

// 出队
TreeNode* dequeue(Queue* q) {
    if (q->front == NULL) return NULL;
    QueueNode* temp = q->front;
    TreeNode* node = temp->treeNode;
    q->front = q->front->next;
    if (q->front == NULL) q->rear = NULL;
    free(temp);
    return node;
}

// 层序遍历
void levelOrder(TreeNode* root) {
    if (root == NULL) return;
    Queue q;
    initQueue(&q);
    enqueue(&q, root);
    
    while (q.front != NULL) {
        TreeNode* current = dequeue(&q);
        printf("%d ", current->data);
        if (current->left) enqueue(&q, current->left);
        if (current->right) enqueue(&q, current->right);
    }
}

避坑指南:我曾经在嵌入式设备上实现层序遍历,忘了释放队列节点内存,结果跑了一周后系统内存耗尽。记住:dequeue 里一定要 free 掉节点。

三、根据遍历序列重建二叉树

这个知识点很有意思。给你两个遍历序列,你能把原来的二叉树还原出来吗?我面试时经常拿这道题考人,能答对的不到一半。

核心原理:前序序列的第一个节点一定是根节点。中序序列中,根节点左边是左子树,右边是右子树。递归下去,就能重建整棵树。

看代码:

// 根据前序和中序重建二叉树
TreeNode* buildTree(int* preorder, int* inorder, int preStart, int preEnd, 
                    int inStart, int inEnd) {
    if (preStart > preEnd || inStart > inEnd) return NULL;
    
    // 前序第一个节点是根
    TreeNode* root = (TreeNode*)malloc(sizeof(TreeNode));
    root->data = preorder[preStart];
    root->left = root->right = NULL;
    
    // 在中序中找到根的位置
    int rootIndex;
    for (rootIndex = inStart; rootIndex <= inEnd; rootIndex++) {
        if (inorder[rootIndex] == root->data) break;
    }
    
    int leftSize = rootIndex - inStart;  // 左子树节点数
    
    // 递归构建左右子树
    root->left = buildTree(preorder, inorder, 
                          preStart + 1, preStart + leftSize,
                          inStart, rootIndex - 1);
    root->right = buildTree(preorder, inorder,
                           preStart + leftSize + 1, preEnd,
                           rootIndex + 1, inEnd);
    
    return root;
}

注意:重建二叉树的前提是序列中所有节点值唯一。如果有重复值,你就没法确定根节点在中序中的位置了。我当年做项目时吃过这个亏,后来在序列化时强制加上了节点ID。

四、知识体系总览

为了让大家看得更清楚,我画了一张图,把今天讲的内容串起来:

二叉树遍历 递归遍历(深度优先) 前序:根 → 左 → 右 中序:左 → 根 → 右 后序:左 → 右 → 根 层序遍历(广度优先) 队列实现(FIFO) 重建二叉树:前序+中序 → 还原树结构

五、实战经验总结

最后,我给大家几个实战建议:

  • 递归遍历:代码简洁,但深度太大时可能栈溢出。嵌入式开发中,我一般限制树深度不超过100层。
  • 层序遍历:适合按层处理数据,比如打印树形结构、计算树宽。队列实现时注意内存管理。
  • 重建二叉树:面试高频题,理解递归分治思想是关键。实际项目中,我常用它来做数据序列化和反序列化。

一句话总结:遍历是二叉树操作的基础,递归是灵魂,队列是工具,重建是进阶。把这三种遍历吃透,二叉树这块你就拿下了八成。

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