【数据结构与算法】第21篇:二叉树遍历的经典问题:由遍历序列重构二叉树

一、为什么能唯一确定一棵二叉树

1.1 需要哪些序列

  • 前序+中序:可以唯一确定一棵二叉树

  • 后序+中序:可以唯一确定一棵二叉树

  • 前序+后序:不能唯一确定(当节点只有单子树时无法区分左右)

1.2 核心思想

以"前序+中序"为例:

  • 前序遍历的第一个节点是根节点

  • 在中序遍历中,根节点左边是左子树 ,右边是右子树

  • 根据左子树的节点个数,可以在前序序列中划分出左子树和右子树的范围

  • 递归处理左右子树

text

复制代码
前序:[根] [左子树前序] [右子树前序]
中序:[左子树中序] [根] [右子树中序]

二、前序+中序重构二叉树

2.1 手动推导示例

已知:

  • 前序:[1, 2, 4, 5, 3, 6]

  • 中序:[4, 2, 5, 1, 3, 6]

第1步:前序第一个是1 → 根节点为1

第2步 :在中序中找到1,左边 [4,2,5] 是左子树,右边 [3,6] 是右子树

text

复制代码
        1
       / \
  左子树  右子树

第3步 :左子树有3个节点,对应前序中根后面的3个:[2,4,5]

左子树递归:

  • 前序:[2,4,5] → 根是2

  • 中序:[4,2,5] → 2左边是4,右边是5

text

复制代码
        1
       / \
      2   右子树
     / \
    4   5

第4步 :右子树有2个节点,对应前序中剩余:[3,6]

右子树递归:

  • 前序:[3,6] → 根是3

  • 中序:[3,6] → 3左边空,右边是6

text

复制代码
        1
       / \
      2   3
     / \   \
    4   5   6

2.2 代码实现

c

复制代码
#include <stdio.h>
#include <stdlib.h>

typedef struct TreeNode {
    int data;
    struct TreeNode *left;
    struct TreeNode *right;
} TreeNode;

TreeNode* createNode(int value) {
    TreeNode *node = (TreeNode*)malloc(sizeof(TreeNode));
    node->data = value;
    node->left = NULL;
    node->right = NULL;
    return node;
}

// 在中序序列中查找根节点的位置
int findIndex(int *inorder, int start, int end, int value) {
    for (int i = start; i <= end; i++) {
        if (inorder[i] == value) {
            return i;
        }
    }
    return -1;
}

// 前序+中序重构
// preStart, preEnd: 前序序列的起止索引
// inStart, inEnd: 中序序列的起止索引
TreeNode* buildFromPreIn(int *preorder, int preStart, int preEnd,
                         int *inorder, int inStart, int inEnd) {
    if (preStart > preEnd || inStart > inEnd) {
        return NULL;
    }
    
    // 前序第一个是根
    int rootVal = preorder[preStart];
    TreeNode *root = createNode(rootVal);
    
    // 在中序中找到根的位置
    int rootIndex = findIndex(inorder, inStart, inEnd, rootVal);
    int leftSize = rootIndex - inStart;   // 左子树节点个数
    
    // 递归构建左右子树
    root->left = buildFromPreIn(preorder, preStart + 1, preStart + leftSize,
                                inorder, inStart, rootIndex - 1);
    root->right = buildFromPreIn(preorder, preStart + leftSize + 1, preEnd,
                                 inorder, rootIndex + 1, inEnd);
    
    return root;
}

// 辅助函数:包装调用
TreeNode* buildTreePreIn(int *preorder, int *inorder, int n) {
    return buildFromPreIn(preorder, 0, n - 1, inorder, 0, n - 1);
}

三、后序+中序重构二叉树

3.1 手动推导示例

已知:

  • 后序:[4, 5, 2, 6, 3, 1]

  • 中序:[4, 2, 5, 1, 3, 6]

第1步:后序最后一个是1 → 根节点为1

第2步 :在中序中找到1,左边 [4,2,5] 是左子树,右边 [3,6] 是右子树

第3步 :左子树有3个节点,对应后序中前面的3个:[4,5,2]

左子树递归:

  • 后序:[4,5,2] → 根是2

  • 中序:[4,2,5] → 2左边是4,右边是5

第4步 :右子树有2个节点,对应后序中剩余:[6,3]

右子树递归:

  • 后序:[6,3] → 根是3

  • 中序:[3,6] → 3左边空,右边是6

结果与之前相同。

3.2 代码实现

c

复制代码
// 后序+中序重构
// postStart, postEnd: 后序序列的起止索引
// inStart, inEnd: 中序序列的起止索引
TreeNode* buildFromPostIn(int *postorder, int postStart, int postEnd,
                          int *inorder, int inStart, int inEnd) {
    if (postStart > postEnd || inStart > inEnd) {
        return NULL;
    }
    
    // 后序最后一个是根
    int rootVal = postorder[postEnd];
    TreeNode *root = createNode(rootVal);
    
    // 在中序中找到根的位置
    int rootIndex = findIndex(inorder, inStart, inEnd, rootVal);
    int leftSize = rootIndex - inStart;   // 左子树节点个数
    int rightSize = inEnd - rootIndex;    // 右子树节点个数
    
    // 递归构建左右子树
    root->left = buildFromPostIn(postorder, postStart, postStart + leftSize - 1,
                                 inorder, inStart, rootIndex - 1);
    root->right = buildFromPostIn(postorder, postEnd - rightSize, postEnd - 1,
                                  inorder, rootIndex + 1, inEnd);
    
    return root;
}

// 辅助函数
TreeNode* buildTreePostIn(int *postorder, int *inorder, int n) {
    return buildFromPostIn(postorder, 0, n - 1, inorder, 0, n - 1);
}

四、完整代码演示

c

复制代码
#include <stdio.h>
#include <stdlib.h>

typedef struct TreeNode {
    int data;
    struct TreeNode *left;
    struct TreeNode *right;
} TreeNode;

TreeNode* createNode(int value) {
    TreeNode *node = (TreeNode*)malloc(sizeof(TreeNode));
    node->data = value;
    node->left = NULL;
    node->right = NULL;
    return node;
}

int findIndex(int *arr, int start, int end, int value) {
    for (int i = start; i <= end; i++) {
        if (arr[i] == value) return i;
    }
    return -1;
}

// 前序+中序
TreeNode* buildFromPreIn(int *preorder, int preStart, int preEnd,
                         int *inorder, int inStart, int inEnd) {
    if (preStart > preEnd || inStart > inEnd) return NULL;
    
    int rootVal = preorder[preStart];
    TreeNode *root = createNode(rootVal);
    
    int rootIndex = findIndex(inorder, inStart, inEnd, rootVal);
    int leftSize = rootIndex - inStart;
    
    root->left = buildFromPreIn(preorder, preStart + 1, preStart + leftSize,
                                inorder, inStart, rootIndex - 1);
    root->right = buildFromPreIn(preorder, preStart + leftSize + 1, preEnd,
                                 inorder, rootIndex + 1, inEnd);
    return root;
}

// 后序+中序
TreeNode* buildFromPostIn(int *postorder, int postStart, int postEnd,
                          int *inorder, int inStart, int inEnd) {
    if (postStart > postEnd || inStart > inEnd) return NULL;
    
    int rootVal = postorder[postEnd];
    TreeNode *root = createNode(rootVal);
    
    int rootIndex = findIndex(inorder, inStart, inEnd, rootVal);
    int leftSize = rootIndex - inStart;
    
    root->left = buildFromPostIn(postorder, postStart, postStart + leftSize - 1,
                                 inorder, inStart, rootIndex - 1);
    root->right = buildFromPostIn(postorder, postStart + leftSize, postEnd - 1,
                                  inorder, rootIndex + 1, inEnd);
    return root;
}

// 遍历验证
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);
}

void freeTree(TreeNode *root) {
    if (root == NULL) return;
    freeTree(root->left);
    freeTree(root->right);
    free(root);
}

int main() {
    // 原始树的前序、中序、后序
    int preorder_seq[] = {1, 2, 4, 5, 3, 6};
    int inorder_seq[] = {4, 2, 5, 1, 3, 6};
    int postorder_seq[] = {4, 5, 2, 6, 3, 1};
    int n = sizeof(preorder_seq) / sizeof(preorder_seq[0]);
    
    printf("=== 前序+中序重构 ===\n");
    printf("前序: ");
    for (int i = 0; i < n; i++) printf("%d ", preorder_seq[i]);
    printf("\n中序: ");
    for (int i = 0; i < n; i++) printf("%d ", inorder_seq[i]);
    printf("\n");
    
    TreeNode *tree1 = buildTreePreIn(preorder_seq, inorder_seq, n);
    printf("重构后的前序遍历: ");
    preorder(tree1);
    printf("\n重构后的中序遍历: ");
    inorder(tree1);
    printf("\n");
    
    printf("\n=== 后序+中序重构 ===\n");
    printf("后序: ");
    for (int i = 0; i < n; i++) printf("%d ", postorder_seq[i]);
    printf("\n中序: ");
    for (int i = 0; i < n; i++) printf("%d ", inorder_seq[i]);
    printf("\n");
    
    TreeNode *tree2 = buildTreePostIn(postorder_seq, inorder_seq, n);
    printf("重构后的后序遍历: ");
    postorder(tree2);
    printf("\n重构后的中序遍历: ");
    inorder(tree2);
    printf("\n");
    
    freeTree(tree1);
    freeTree(tree2);
    
    return 0;
}

运行结果:

text

复制代码
=== 前序+中序重构 ===
前序: 1 2 4 5 3 6 
中序: 4 2 5 1 3 6 
重构后的前序遍历: 1 2 4 5 3 6 
重构后的中序遍历: 4 2 5 1 3 6 

=== 后序+中序重构 ===
后序: 4 5 2 6 3 1 
中序: 4 2 5 1 3 6 
重构后的后序遍历: 4 5 2 6 3 1 
重构后的中序遍历: 4 2 5 1 3 6 

五、递归分治过程图解

以"前序+中序"为例,递归划分过程:

text

复制代码
第1层:
前序 [1 | 2 4 5 | 3 6]
中序 [4 2 5 | 1 | 3 6]
     左子树 根 右子树

第2层(左子树):
前序 [2 | 4 | 5]
中序 [4 | 2 | 5]
     左 根 右

第2层(右子树):
前序 [3 | 6]
中序 [3 | 6]
     根 右

六、复杂度分析

操作 时间复杂度 空间复杂度
查找根节点 O(n)(每层遍历) -
优化版(哈希表) O(1) O(n)
整体 O(n²) 最坏 / O(n) 优化 O(n)(递归栈+哈希表)

优化方法:用哈希表存储中序序列中每个值的位置,查找根节点变为O(1)。


七、小结

这一篇我们学习了由遍历序列重构二叉树:

已知序列 可行性 核心思路
前序+中序 前序确定根,中序分左右
后序+中序 后序确定根,中序分左右
前序+后序 单子树时无法区分左右

递归分治模板

text

复制代码
1. 从前序(或后序)中取出根节点
2. 在中序中找到根的位置
3. 计算左子树节点个数
4. 递归构建左子树
5. 递归构建右子树

下一篇我们讲线索二叉树。


八、思考题

  1. 已知前序 [1,2,3],后序 [3,2,1],能唯一确定一棵二叉树吗?为什么?

  2. 如果二叉树节点值可以重复,前序+中序还能唯一确定吗?

  3. 尝试用哈希表优化查找根节点的过程,将时间复杂度降到O(n)。

  4. 给定层序和中序,如何重构二叉树?

欢迎在评论区讨论你的答案。

相关推荐
qeen871 天前
【算法笔记】差分与经典例题解析
c语言·c++·笔记·学习·算法·差分
HZ·湘怡1 天前
双链表 -- 带头 双向 循环 链表
数据结构·链表·双链表
kronos.荒1 天前
动态规划——整数拆分(python)
python·算法·动态规划
ACCELERATOR_LLC1 天前
【DataWhale组队学习】DIY-LLM Task3 语言模型架构和训练的技术细节
人工智能·学习·语言模型·transformer
嵌入式小企鹅1 天前
Kimi K2.6开源对标GPT-5.4、英飞凌AURIX拥抱RISC-V、工信部定调太空算力
人工智能·学习·开源·嵌入式·模型·半导体·昇腾
椰羊~王小美1 天前
C、Java、Go、Python 对比
java·c语言
cici158741 天前
基于Koopman模型预测控制的非线性流控制数据驱动框架
算法
6Hzlia1 天前
【Hot 100 刷题计划】 LeetCode 416. 分割等和子集 | C++ 0-1背包 1D空间极致优化
c++·算法·leetcode
23471021271 天前
4.22 学习笔记
软件测试·笔记·python·学习
穿条秋裤到处跑1 天前
每日一道leetcode(2026.04.21):执行交换操作后的最小汉明距离
java·算法·leetcode