当前位置: 首页 > news >正文

算法竞赛:从遍历序列完美重建二叉树(先序/后序 + 中序)

算法竞赛:从遍历序列完美重建二叉树(先序/后序 + 中序)

在数据结构与算法竞赛(如 PTA 天梯赛、PAT、ICPC)中,“给定两个遍历序列,求第三个序列(或层序遍历)”几乎是树形结构中最经典的必考题型。

很多初学者在建树时,经常被复杂的递归边界(L, R 的加减一)绕晕,甚至引发段错误。本文将从核心逻辑出发,带你彻底攻克“先序+中序”与“后序+中序”的建树难题,并提供极致精简的 C++ 竞赛模板。

一、 核心原理解析:为什么核心永远是“中序”?

要还原一棵二叉树,我们必须明确三种遍历序列的“职能”:

  • 先序遍历 (Preorder)[根节点] [左子树的先序] [右子树的先序]。它的唯一作用就是告诉我们谁是当前的根节点(永远在序列最前面)。
  • 后序遍历 (Postorder)[左子树的后序] [右子树的后序] [根节点]。它的作用和先序一样,也是提供根节点(永远在序列最后面)。
  • 中序遍历 (Inorder)[左子树的中序] [根节点] [右子树的中序]。这是建树的灵魂!只有拿着先序或后序提供的“根节点”,在中序序列中找到它,才能将数组劈成两半,精确划分出左子树和右子树的长度

经典面试题:为什么“先序 + 后序”无法还原二叉树?

因为当一个节点只有一个孩子时,先序和后序无法区分这个孩子到底是左孩子还是右孩子(它们在这两种序列中的相对位置是一样的),因此必须依赖中序遍历来做基准划分。

二、 竞赛级优化:由\(O(N^2)\)优化为 \(O(N)\)

在传统的课本代码中,每次在中序数组中找根节点,都需要写一个 for 循环去遍历查找。这会导致整个建树的时间复杂度退化为 \(O(N^2)\)

降维打击优化:在读入中序序列时,直接用一个 std::unordered_map<int, int>(如果节点值较小,直接用数组 pos[N] 更快)把“节点值”和“它在中序序列中的下标”绑定起来。这样每次找根节点只需 \(O(1)\) 的时间,总复杂度骤降至 \(O(N)\)

三、 先序 + 中序建树:核心逻辑

1. 核心边界推导

假设我们在递归函数中,当前要处理的中序区间是 [il, ir]先序区间是 [pl, pr]

  1. 毫无疑问,当前的根节点是 root_val = pre[pl]
  2. 通过哈希表,我们瞬间查到这个根在中序里的下标是 k = pos[root_val]
  3. 计算左子树长度len = k - il
  4. 递归切割
    • 左子树的中序区间:[il, k - 1]
    • 左子树的先序区间:[pl + 1, pl + len]
    • 右子树的中序区间:[k + 1, ir]
    • 右子树的先序区间:[pl + len + 1, pr]

2. C++ 静态数组建树模板(拒绝 new 节点)

C++

#include <iostream>
#include <unordered_map>
using namespace std;const int N = 100010;
int pre[N], in[N];
int l[N], r[N]; // 静态数组代替左右指针,l[i] 表示节点 i 的左孩子
unordered_map<int, int> pos;// 返回当前子树的根节点值
int build(int il, int ir, int pl, int pr) {if (il > ir) return 0; // 空树返回 0(假设节点值 > 0)int root = pre[pl];         // 先序的第一个就是根int k = pos[root];          // $O(1)$ 找到根在中序的下标int len = k - il;           // 算出左子树的长度// 递归建立左右子树,并挂在当前 root 下l[root] = build(il, k - 1, pl + 1, pl + len);r[root] = build(k + 1, ir, pl + len + 1, pr);return root;
}int main() {int n;cin >> n;for (int i = 0; i < n; i++) cin >> pre[i];for (int i = 0; i < n; i++) {cin >> in[i];pos[in[i]] = i; // 记录中序序列中每个值的下标}int root = build(0, n - 1, 0, n - 1);// 此处 root 即为整棵树的根,后续可以直接跑 BFS 进行层序遍历return 0;
}

四、 后序 + 中序建树

后序遍历的区别仅仅在于:根节点在后序区间的最后面

1. 核心边界推导

假设当前中序区间是 [il, ir]后序区间是 [pl, pr]

  1. 当前的根节点是 root_val = post[pr]
  2. 在中序找根下标 k = pos[root_val]
  3. 计算左子树长度len = k - il
  4. 递归切割
    • 左子树的中序区间:[il, k - 1]
    • 左子树的后序区间:[pl, pl + len - 1] (注意这里是从 pl 开始数 len 个)
    • 右子树的中序区间:[k + 1, ir]
    • 右子树的后序区间:[pl + len, pr - 1] (最后一位 pr 是根,要排除)

2. C++ 模板代码

C++

#include <iostream>
#include <unordered_map>
using namespace std;const int N = 100010;
int post[N], in[N];
int l[N], r[N];
unordered_map<int, int> pos;int build(int il, int ir, int pl, int pr) {if (il > ir) return 0;int root = post[pr];        // 后序的最后一个是根int k = pos[root];int len = k - il;           // 左子树长度l[root] = build(il, k - 1, pl, pl + len - 1);r[root] = build(k + 1, ir, pl + len, pr - 1);return root;
}

五、 实战拔高:建好树后该干嘛?(层序遍历输出)

在绝大多数的 PTA 题目(例如经典的 L2-006 树的遍历)中,题目通常会给你后序和中序,要求你输出层序遍历(Level-order Traversal)

有了上面的静态数组 l[N]r[N],层序遍历也就是一个极其简单的 BFS(广度优先搜索):

C++

#include <queue>
#include <vector>void bfs(int root) {queue<int> q;vector<int> res;q.push(root);while (!q.empty()) {int t = q.front();q.pop();res.push_back(t);if (l[t] != 0) q.push(l[t]); // 压入左孩子if (r[t] != 0) q.push(r[t]); // 压入右孩子}// 格式化输出for (int i = 0; i < res.size(); i++) {cout << res[i] << (i == res.size() - 1 ? "" : " ");}
}

六、 总结

无论题目怎么变形,核心口诀只有三步:

  1. 看前/后序,抓根节点。
  2. 拿根节点去中序定位,计算左子树长度 len
  3. 基于 len 严谨地切分区间,递归往下走。

彻底掌握了这套模板与区间划分思想,你就能在机试中做到此类问题5分钟内 Bug-free 满分通过,为后续更复杂的图论和树形 DP 题目节省出大量时间!

这篇博客的静态数组写法(l[N], r[N])是极具竞赛风格的。它完美避开了动态内存分配(new TreeNode)带来的时间和空间开销,在算法竞赛这种毫秒必争的场景下表现极其优异。希望对大家有所帮助!

http://www.jsqmd.com/news/1019666/

相关文章:

  • 华为GPON网络‘流氓ONU’处理全记录:从告警闪现到分光器侧精准‘抓捕’
  • 2026 海口业主防水避坑指南:苏易修缮本地化精工防水,工艺 / 报价 / 竞品全方位对比 - 苏易修缮
  • 2026 漳州室内家装装潢靠谱装修公司参考名录 - 海棠依旧大
  • 别再被Cartographer的.lua文件搞懵了!手把手教你读懂并调优revo_lds.lua核心参数
  • 2026石家庄市高邑县家里卫生间漏水、阳台漏水、楼顶漏水、阳台漏水、地下室渗水、阳光房漏水各种房屋漏水情况不用愁!全屋各类渗水问题正规服务商盘点 - 防水百科
  • 告别命令行恐惧:用Portainer可视化面板管理你的ZeroTier Docker容器
  • 2026甄选:南京汽车空调专业维修服务公司精准排查与高效充氟指南 - 品牌发掘
  • 别再死记硬背了!图解哈密顿回路与欧拉回路的本质区别(附LeetCode刷题指北)
  • 2026 永州业主防水避坑指南:苏易修缮本地化精工防水,工艺 / 报价 / 竞品全方位对比 - 苏易修缮
  • DS4Windows深度解析:专业级手柄校准与配置实战指南
  • 2026吴忠卫生间免砸砖防水、楼顶漏水、外墙渗水、地下室阳光房渗漏;专业防水公司为您排忧解难,线上质保,售后无忧。房屋漏水不再愁,24小时一站式快速维修。 - 企业资讯
  • 2026年 东莞离心盘/离心盘送料机/螺丝离心盘/瓶盖离心盘厂家推荐排行榜:高精度供料与稳定效率之选 - 品牌发掘
  • LLaVA多模态实战入门:从零部署视觉语言模型
  • 从‘Failed to build wheel’到成功安装:一个PyArrow报错引发的Python包生态思考
  • FreeRTOS 3.1.0在S32K344上的踩坑实录:从驱动版本冲突到配置界面打不开
  • 2026石家庄市长安区家里卫生间漏水、阳台漏水、楼顶漏水、阳台漏水、地下室渗水、阳光房漏水各种房屋漏水情况不用愁!全屋各类渗水问题正规服务商盘点 - 防水百科
  • 2026年 南京自动变速箱故障维修:专业技术与精细化修复的质保之选 - 品牌发掘
  • 2026年 南京汽车维修推荐榜:专业钣喷/深度养护/变速箱专修,高品质养车口碑之选 - 品牌发掘
  • 2026重庆市丰都县家里卫生间漏水、阳台漏水、楼顶漏水、阳台漏水、地下室渗水、阳光房漏水各种房屋漏水情况不用愁!全屋各类渗水问题正规服务商盘点 - 防水百科
  • 2026济源卫生间免砸砖防水、楼顶漏水、外墙渗水、地下室阳光房渗漏;专业防水公司为您排忧解难,线上质保,售后无忧。房屋漏水不再愁,24小时一站式快速维修。 - 企业资讯
  • 聚焦专业高效与权益保障:2026年四川成都婚姻财产分割/法律咨询/房产纠纷/会见/离婚律师/经济纠纷/合同纠纷/辩护五大律师事务所盘点 - 十大品牌榜
  • 2026平凉卫生间免砸砖防水、楼顶漏水、外墙渗水、地下室阳光房渗漏;专业防水公司为您排忧解难,线上质保,售后无忧。房屋漏水不再愁,24小时一站式快速维修。 - 企业资讯
  • MPC8544E中断控制器架构解析与实战配置指南
  • WindowsCleaner终极指南:3分钟解决C盘爆红的免费开源清理神器
  • 深耕珠海二十载,通达管道疏通。用实力守护城市 和每一个家庭的生活 - 园子一号
  • d2s-editor:暗黑破坏神2存档编辑的革命性工具,解锁单机游戏无限可能
  • 2026石家庄市栾城区家里卫生间漏水、阳台漏水、楼顶漏水、阳台漏水、地下室渗水、阳光房漏水各种房屋漏水情况不用愁!全屋各类渗水问题正规服务商盘点 - 防水百科
  • Day47
  • 广州AI智能体开发公司:互诚科技的信誉与实力解析 - 奔跑123
  • 2026石家庄市桥西区家里卫生间漏水、阳台漏水、楼顶漏水、阳台漏水、地下室渗水、阳光房漏水各种房屋漏水情况不用愁!全屋各类渗水问题正规服务商盘点 - 防水百科