当前位置: 首页 > 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.rkmt.cn/news/1531513.html

相关文章:

  • 你的Windows电脑还在卡顿?这款神器让系统重获新生!
  • 跨境电商独立站技术选型:为什么React+Vue+Laravel成为主流?
  • 避坑指南:ESP8266 EEPROM读写与WiFi连接的那些‘坑’(附串口中断冲突解决方案)
  • MySQL MVCC 多版本并发控制
  • 告别命令行恐惧:用Portainer可视化面板管理你的ZeroTier Docker容器
  • 2026 漳州室内家装装潢靠谱装修公司参考名录 - 海棠依旧大
  • 2026重庆市丰都县家里卫生间漏水、阳台漏水、楼顶漏水、阳台漏水、地下室渗水、阳光房漏水各种房屋漏水情况不用愁!全屋各类渗水问题正规服务商盘点 - 防水百科
  • 2026石家庄市长安区家里卫生间漏水、阳台漏水、楼顶漏水、阳台漏水、地下室渗水、阳光房漏水各种房屋漏水情况不用愁!全屋各类渗水问题正规服务商盘点 - 防水百科
  • 2026石家庄市栾城区家里卫生间漏水、阳台漏水、楼顶漏水、阳台漏水、地下室渗水、阳光房漏水各种房屋漏水情况不用愁!全屋各类渗水问题正规服务商盘点 - 防水百科
  • WindowsCleaner终极指南:3分钟解决C盘爆红的免费开源清理神器
  • 2026石家庄市桥西区家里卫生间漏水、阳台漏水、楼顶漏水、阳台漏水、地下室渗水、阳光房漏水各种房屋漏水情况不用愁!全屋各类渗水问题正规服务商盘点 - 防水百科
  • 成都洗衣机不排水怎么办?维修费用和平台选择实测分享 - 简单到家
  • 2026石家庄市深泽县家里卫生间漏水、阳台漏水、楼顶漏水、阳台漏水、地下室渗水、阳光房漏水各种房屋漏水情况不用愁!全屋各类渗水问题正规服务商盘点 - 防水百科
  • 2026 烟台包装厂家哪家靠谱,本地口碑推荐 - 烟台日升包装优选 - 海棠依旧大
  • 合肥空调不制热找谁修靠谱?亲测260块搞定的经验分享 - 简单到家
  • 2026重庆市巫溪县家里卫生间漏水、阳台漏水、楼顶漏水、阳台漏水、地下室渗水、阳光房漏水各种房屋漏水情况不用愁!全屋各类渗水问题正规服务商盘点 - 防水百科
  • 2026石家庄市井陉县家里卫生间漏水、阳台漏水、楼顶漏水、阳台漏水、地下室渗水、阳光房漏水各种房屋漏水情况不用愁!全屋各类渗水问题正规服务商盘点 - 防水百科
  • 如何用AI Agent处理PR、写单测、修Bug,以及IT从业者的角色从“码农”向“架构师+审阅者”转变的一些经验与感受分享
  • iStoreOS下Home Assistant安装HACS,网络不通?试试这个离线脚本(附完整命令)
  • 2026 石家庄业主防水避坑指南:苏易修缮本地化精工防水,工艺 / 报价 / 竞品全方位对比 - 苏易修缮
  • 福建亲子游攻略!带娃省心出行,认准本地靠谱旅游公司天天周游国旅 - 热点速览
  • 【无人机控制】基于PID和LQR控制智能农业无人机热点靶向农药喷洒附代码
  • 2026克拉玛依卫生间免砸砖防水、楼顶漏水、外墙渗水、地下室阳光房渗漏;专业防水公司为您排忧解难,线上质保,售后无忧。房屋漏水不再愁,24小时一站式快速维修。 - 企业资讯
  • E-Ink Launcher:3个技巧让你的墨水屏设备体验翻倍
  • HsMod终极指南:解锁炉石传说55项隐藏功能,打造个性化游戏体验
  • Hugging Face连不上?手把手教你离线配置bert-base-uncased模型(附RSTNet复现避坑指南)
  • 2026甄选:南京拖车救援服务公司推荐——道路紧急搭电/困境脱险与全天候快速响应品牌机构 - 品牌发掘
  • 2026石家庄市行唐县家里卫生间漏水、阳台漏水、楼顶漏水、阳台漏水、地下室渗水、阳光房漏水各种房屋漏水情况不用愁!全屋各类渗水问题正规服务商盘点 - 防水百科
  • e300处理器缓存锁定与总线窥探:嵌入式实时系统的确定性保障
  • 2026 邢台业主防水避坑指南:苏易修缮本地化精工防水,工艺 / 报价 / 竞品全方位对比 - 苏易修缮