C++实现二叉搜索树BST的中序前驱与后继查找 _ 树节点搜索逻辑【源码】
二叉搜索树中序前驱与后继的查找需分情况:有左子树时前驱为左子树最右节点,否则向上寻找第一个作为父节点右孩子的祖先;后继逻辑对称。若无parent指针,则需遍历或模拟路径。注意判空及边界情况。
二叉搜索树(BST)的中序前驱与后继查找,是面试和工程中都会碰到的经典操作。很多人一上来就记口诀:“前驱是左子树的最右节点,后继是右子树的最左节点”。但实际情况要复杂得多——因为当前节点可能没有左子树或右子树,这时候就得向上回溯,找祖先节点。下面我们把这层逻辑拆开讲清楚。

中序前驱为什么不能只看左子树最大节点
中序前驱,说白了就是中序遍历中紧挨着当前节点、比它小的那个节点。它不一定在左子树里。当节点有左子树时,前驱确实是左子树中最右边的那个节点(一路向右走到头);但如果节点没有左子树呢?那就只能向上走,找到第一个“它是父节点的右孩子”的祖先节点,那个父节点就是前驱。
举个例子:一棵BST中,根节点如果压根没有左子树,它的前驱只能从祖先里找——从根往上回溯,直到某个节点是它父节点的右孩子,此时父节点就是目标。如果一路回溯到根都没找到(比如根节点本身就是最左节点),那就没有前驱。
实操建议:
- 从目标节点出发,沿着
parent指针向上回溯,找第一个满足「该节点是其父节点右孩子」的祖先,那个父节点就是前驱。 - 若节点有左子树,则前驱一定是左子树中的最右节点(不断走
right直到为空)。 - 必须保证每个节点存有
parent指针,否则无法高效向上查找。如果没有parent,就只能先做一次完整中序遍历,把节点存进数组再查索引,时间复杂度 O(n)。
中序后继的两种路径怎么选
后继的逻辑和前驱正好对称。中序后继就是中序遍历中紧挨着当前节点、比它大的那个节点。依然分两种情况:
- 若节点有右子树:后继 = 右子树中最左节点(循环走
left直到为空)。 - 若节点无右子树:向上找第一个「该节点是其父节点左孩子」的祖先,那个父节点即为后继。
- 注意边界:如果节点是整棵树最右节点(既没有右子树,沿着
parent一路向上都是父节点的右孩子),那后继就不存在,返回nullptr。
这里有个常见坑:写代码时容易崩溃,出现 Segmentation fault。十有八九是在访问 parent->left 或 parent->right 之前没判空。建议每次使用 parent 之前先检查是否为 nullptr,这个习惯能省下不少调试时间。
查找前驱/后继时 parent 指针为空怎么办
很多教材实现的BST节点结构并不带 parent 字段。这时候想用 O(h) 时间完成查询基本没戏,只能降级处理。有哪些思路?
- 用栈手动模拟递归:从根开始,沿左链压栈,直到目标节点或更早位置;过程中记录路径,便于定位前驱或后继。这需要自己维护路径,代码量稍大。
- 或者干脆做一次完整中序遍历(比如
std::vector存节点指针),然后在线性表里用二分查找或线性扫描找目标索引。空间 O(n),时间 O(n),适合离线批量查询的场景。 - 如果系统里高频次地需要查前驱/后继,那不如直接重构节点结构,加上
parent字段。虽然改数据结构麻烦点,但比每次遍历划算得多。
std::set 迭代器的 next/prev 本质是不是 BST 前驱后继
答案是“是”,但人家不是你那小手写的简单BST。libstdc++ 和 libc++ 里的 std::set 底层是红黑树,std::next(it) 和 std::prev(it) 内部实现的正是前驱/后继操作,且常数均摊。
几个值得注意的点:
- 它们不依赖用户手动维护的
parent指针,而是利用红黑树的节点结构(颜色位、子节点关系)在 O(log n) 时间内完成,且没有额外空间开销。 - 但你没法直接复用这套逻辑——标准库不暴露红黑树节点结构。
std::set::iterator是封装过的,不能解引用成原始节点指针来操作。 - 想验证行为差异?可以写个对比测试:往手写的BST和
std::set里插入相同序列,再用相同 key 查后继,输出地址或值来比对。
最后说一句容易被忽略的事:手写BST里,parent 指针的维护必须贯穿所有修改操作——插入、删除,缺一不可。漏掉任何一处对 parent 指针的更新,前驱/后继查询就会静默出错,这种 bug 极难排查。所以要么用严谨的测试覆盖,要么干脆考虑带 parent 字段的工程实现。


































