【数据结构与算法】第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. 给定层序和中序,如何重构二叉树?

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

相关推荐
信奥胡老师2 小时前
P1255 数楼梯
开发语言·数据结构·c++·学习·算法
夜幕下的ACM之路2 小时前
一、基础知识学习(Transformer + 上下文窗口 + Token 计算 + Embedding 向量)
人工智能·学习·transformer·embedding
GHL2842710902 小时前
Base64学习
学习
知识分享小能手2 小时前
MongoDB入门学习教程,从入门到精通,MongoDB创建副本集知识点梳理(10)
数据库·学习·mongodb
爱睡懒觉的焦糖玛奇朵3 小时前
【工业级落地算法之人员摔倒检测算法详解】
人工智能·python·深度学习·神经网络·算法·yolo·目标检测
小辉同志3 小时前
78. 子集
算法·leetcode·深度优先
星幻元宇VR3 小时前
VR动感科普单车:让交通安全教育更真实、更有效
科技·学习·安全·生活·vr
Book思议-3 小时前
【数据结构】二叉树入门全解:从定义、性质到经典真题
数据结构·算法·二叉树
Mem0rin3 小时前
[Java/数据结构]线性表之链表
java·数据结构·链表