
教程文档示例工程教育【免费下载链接】hello-algo《Hello 算法》动画图解、一键运行的数据结构与算法教程。支持简中、繁中、English、日本語提供 Python, Java, C, C, C#, JS, Go, Swift, Rust, Ruby, Kotlin, TS, Dart 等代码实现项目地址https://gitcode.com/GitHub_Trending/he/hello-algo点击查看免费下载导读本篇技术指南围绕 hello-algo 仓库中“构建二叉树问题”build_tree 分治案例展开系统讲解如何仅凭一棵二叉树的前序遍历preorder与中序遍历inorder序列借助分治思想递归还原整棵二叉树。读完本文你将掌握分治问题的判定方法、子树区间的指针变量刻画技巧、哈希表加速索引查询的实现细节以及该算法在 Python、Java、C、C、Go、Rust 等多语言实现中的统一结构与复杂度边界。问题定义从两组遍历序列还原二叉树在二叉树的遍历中前序遍历、中序遍历与后序遍历分别以根左右左根右左右根的顺序访问节点。其中前序遍历与中序遍历的组合可以唯一确定一棵二叉树前提是树中没有值重复的节点。本问题可形式化描述为给定一棵二叉树的前序遍历序列preorder和中序遍历序列inorder请从中构建二叉树返回二叉树的根节点。假设二叉树中没有值重复的节点。以 hello-algo 章节docs/chapter_divide_and_conquer/build_binary_tree_problem.md中的经典示例为例目标二叉树的根节点为 3左子节点为 9右子节点为 2节点 2 的左子节点为 1、右子节点为 7对应前序遍历为[3, 9, 2, 1, 7]中序遍历为[9, 3, 1, 2, 7]。为什么是分治问题三条判定准则在动手写代码之前先确认根据前序与中序遍历构建二叉树为什么是一个典型的分治问题。分治算法的成立依赖三个条件本问题恰好全部满足问题可以分解原问题构建整棵树可以划分为两个规模更小的子问题——构建左子树、构建右子树外加一步初始化根节点的操作。而每一棵子树子问题又可以用同样的方法继续向下划分直至达到最小子问题空子树时终止。子问题是独立的左子树与右子树在节点集合上互不重叠。构建左子树时只需要关注中序与前序序列中与左子树对应的部分右子树同理二者之间不存在相互依赖。子问题的解可以合并一旦递归得到左子树和右子树子问题的解把它们分别挂接到根节点的left与right引用上即得到原问题的解。从源码结构看这一划分—求解—合并的模式在仓库各语言实现中高度一致例如 codes/python/chapter_divide_and_conquer/build_tree.py 中的dfs()函数就是典型的自顶向下递归分解、自底向上合并引用的过程。如何划分子树根节点定位与序列切分确认可以使用分治之后核心难点变为如何通过前序序列与中序序列来划分左子树和右子树根据遍历定义两组序列天然都可以切成三段前序遍历[ 根节点 | 左子树 | 右子树 ]例如示例树对应[ 3 | 9 | 2 1 7 ]中序遍历[ 左子树 | 根节点 | 右子树 ]例如示例树对应[ 9 | 3 | 1 2 7 ]。以示例数据为例划分过程分三步前序遍历的首元素3就是根节点的值在inorder中查找根节点3的索引利用该索引可将inorder划分为[ 9 | 3 | 1 2 7 ]根据inorder的划分结果可知左子树与右子树的节点数量分别为 1 和 3据此可将preorder划分为[ 3 | 9 | 2 1 7 ]。用三个变量描述子树区间上面得到的是一次划分的结果。为了把这种划分递归地应用到每一层子树上需要用变量把当前树的根节点在preorder中的位置以及子树在inorder中的区间精确描述出来。hello-algo 章节采用了三个指针变量$i$当前树的根节点在preorder中的索引$m$当前树的根节点在inorder中的索引$[l, r]$当前树在inorder中的索引区间。借助这三个变量当前树、左子树、右子树的区间可以统一表达为下表子树根节点在preorder中的索引子树在inorder中的索引区间当前树$i$$[l, r]$左子树$i 1$$[l, m-1]$右子树$i 1 (m - l)$$[m1, r]$其中右子树根节点索引中的(m - l)含义是左子树的节点数量因为前序遍历按照根—左—右顺序输出根节点之后先完整走完左子树共m - l个节点随后才是右子树的根节点所以右子树根节点在preorder中的索引为i 1 (m - l)。代码实现哈希表加速 递归分治核心 Python 实现在递归的每一层都要根据根节点的值在inorder中查询其索引 $m$。如果每次都用线性扫描时间复杂度会退化到 $O(n \log n)$ 甚至 $O(n^2)$。hello-algo 的解法是预先构建一张哈希表inorder_map把inorder中每个元素映射到它的下标从而把每次查询降到 $O(1)$。仓库中 Python 实现codes/python/chapter_divide_and_conquer/build_tree.py与 PythonTutor 可视化版本codes/pythontutor/chapter_divide_and_conquer/build_tree.md代码完全一致核心逻辑如下def dfs( preorder: list[int], inorder_map: dict[int, int], i: int, l: int, r: int, ) - TreeNode | None: 构建二叉树分治 # 子树区间为空时终止 if r - l 0: return None # 初始化根节点 root TreeNode(preorder[i]) # 查询 m 从而划分左右子树 m inorder_map[preorder[i]] # 子问题构建左子树 root.left dfs(preorder, inorder_map, i 1, l, m - 1) # 子问题构建右子树 root.right dfs(preorder, inorder_map, i 1 m - l, m 1, r) # 返回根节点 return root def build_tree(preorder: list[int], inorder: list[int]) - TreeNode | None: 构建二叉树 # 初始化哈希表存储 inorder 元素到索引的映射 inorder_map {val: i for i, val in enumerate(inorder)} root dfs(preorder, inorder_map, 0, 0, len(inorder) - 1) return rootbuild_tree入口完成两件事一是用字典推导式在 $O(n)$ 时间内建立inorder元素到索引的映射二是以i 0、l 0、r len(inorder) - 1为初始参数启动递归覆盖整棵树的区间。递归中的关键细节逐行剖析dfs()可以看到几个决定正确性的细节终止条件r - l 0当区间为空时例如左子树区间[l, m-1]中l m-1说明该子树不存在返回None。所有语言的实现都使用这一写法例如 codes/c/chapter_divide_and_conquer/build_tree.c 中的if (r - l 0) return NULL;。根节点创建TreeNode(preorder[i])用前序序列的当前首元素初始化节点对示例数据preorder[0] 3即根节点。区间继承左子树复用当前根节点 1的i与[l, m-1]区间右子树使用i 1 (m - l)与前序索引和[m1, r]区间与上表完全对应。合并解root.left与root.right的赋值发生在子问题返回之后即边在向上归的过程中建立最终逐层返回根节点。递归过程中各个节点是在向下递的过程中被创建的而节点之间的引用关系是在向上归的过程中建立的——这是理解该递归过程的关键心法。各语言实现对照该算法在仓库中拥有完整的多语言实现逻辑结构高度一致仅是语法与容器类型的差异语言源文件哈希表容器备注Javacodes/java/chapter_divide_and_conquer/build_tree.javaHashMapInteger, Integerdfs为静态方法Ccodes/cpp/chapter_divide_and_conquer/build_tree.cppunordered_mapint, int引用传参避免拷贝Ccodes/c/chapter_divide_and_conquer/build_tree.cint inorderMap[MAX_SIZE]假设元素小于 1000用数组模拟哈希表结束后freeGocodes/go/chapter_divide_and_conquer/build_tree.gomap[int]int函数名dfsBuildTreeRustcodes/rust/chapter_divide_and_conquer/build_tree.rsHashMapi32, i32返回OptionRcRefCellTreeNodeC#codes/csharp/chapter_divide_and_conquer/build_tree.csDictionaryint, int使用[Test]单元测试入口JavaScriptcodes/javascript/chapter_divide_and_conquer/build_tree.jsMapCommonJS 模块引入TreeNodeTypeScriptcodes/typescript/chapter_divide_and_conquer/build_tree.tsMapnumber, number显式类型标注Swiftcodes/swift/chapter_divide_and_conquer/build_tree.swift[Int: Int]用reduce(into:)建表Kotlincodes/kotlin/chapter_divide_and_conquer/build_tree.ktHashMapInt?, Int?!!非空断言取mDartcodes/dart/chapter_divide_and_conquer/build_tree.dartMapint, int空安全类型Rubycodes/ruby/chapter_divide_and_conquer/build_tree.rbHasheach_with_index建表各语言 Driver Code 统一使用preorder [3, 9, 2, 1, 7]、inorder [9, 3, 1, 2, 7]作为输入构建完成后打印整棵二叉树。以 Python 为例运行 codes/python/chapter_divide_and_conquer/build_tree.py 会输出前序遍历 [3, 9, 2, 1, 7] 中序遍历 [9, 3, 1, 2, 7] 构建的二叉树为随后以缩进文本形式打印出根节点为 3、左子节点 9、右子树为 2含子节点 1 与 7的树形结构。若希望逐步观察每一层递归的入参与划分结果可以直接打开 PythonTutor 可视化入口文件 codes/pythontutor/chapter_divide_and_conquer/build_tree.md其中内嵌了完整可执行的 Python 代码链接支持单步执行、观察变量i / l / r / m的变化。复杂度分析设树的节点数量为 $n$时间复杂度 $O(n)$初始化哈希表需要遍历inorder耗时 $O(n)$dfs()每个递归函数恰好创建一个节点每个节点创建与引用赋值为 $O(1)$共 $n$ 个节点合计 $O(n)$。哈希表将每次查根节点索引 $m$的操作从线性扫描降为 $O(1)$是保证整体线性复杂度的关键。空间复杂度 $O(n)$哈希表存储inorder中 $n$ 个元素到索引的映射占用 $O(n)$ 空间递归过程使用系统栈最差情况下二叉树退化为链表例如所有节点只有右子树递归深度达到 $n$栈帧空间为 $O(n)$。因此总体空间复杂度为 $O(n)$。易错点与边界条件结合源码与测试输入实践中最容易出错的有三处值重复假设题目明确二叉树中没有值重复的节点这是哈希表映射成立的前提。若存在重复值inorder_map只能记录最后一个下标划分将失效。右子树前序索引的计算i 1 (m - l)中的(m - l)是左子树节点数容易误写成m。注意m是索引、m - l才是数量。区间空判定终止条件必须用r - l 0而非r l的等价简化之外的特殊写法确保如[l, m-1]这类区间在l m-1时正确返回None否则会出现数组越界或空指针。小结构建二叉树问题是分治思想的教科书级案例判定分治三条件 → 用遍历序列切分子树 → 用三个指针变量刻画区间 → 哈希表加速索引查询 → 递归求解与合并。hello-algo 仓库为该问题提供了 12 种语言的完整可运行实现目录 codes/chapter_divide_and_conquer/其配套章节 docs/chapter_divide_and_conquer/build_binary_tree_problem.md 还配有 9 张逐步递归建树过程图与每个递归函数内划分结果的总览图built_tree_overall.png建议结合图片理解递时建节点、归时建边的完整过程。赞分享教程文档示例工程教育【免费下载链接】hello-algo《Hello 算法》动画图解、一键运行的数据结构与算法教程。支持简中、繁中、English、日本語提供 Python, Java, C, C, C#, JS, Go, Swift, Rust, Ruby, Kotlin, TS, Dart 等代码实现项目地址https://gitcode.com/GitHub_Trending/he/hello-algo点击查看免费下载相关推荐Hello 算法分治实战用前序遍历与中序遍历重建二叉树build_tree 代码全解析Hello 算法分治实战用前序遍历与中序遍历重建二叉树build_tree 代码全解析 本文以《Hello 算法》仓库中「构建二叉树问题」配套的分治实现为教程文档示例工程教育Hello 算法从前序与中序遍历序列重建二叉树——分治策略的完整实战解析Hello 算法从前序与中序遍历序列重建二叉树——分治策略的完整实战解析 本文是《Hello 算法》分治一章的配套深度解析聚焦于经典问题从前序pre教程文档示例工程教育根据前序与中序遍历构建二叉树Hello 算法中的分治递归实战剖析根据前序与中序遍历构建二叉树Hello 算法中的分治递归实战剖析 导读 本文围绕《Hello 算法》构建二叉树问题这一经典分治案例展开讲解如何仅凭二叉教程文档示例工程教育上一篇如何让VLC颜值与效率并存自定义皮肤全攻略下一篇Autonomous-Researcher快速入门10分钟搭建自主AI研究平台创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考