二叉搜索树的中序前驱查找
字数 1676 2025-12-05 19:47:22
二叉搜索树的中序前驱查找
题目描述
在二叉搜索树(BST)中,给定一个节点,找出其中序遍历序列中的前驱节点。中序遍历的前驱节点定义为:在中序遍历顺序下,比该节点值小的最大节点。如果该节点没有前驱(例如它是树中的最小节点),则返回 null。
知识点
- 二叉搜索树的性质:左子树所有节点值 < 根节点值 < 右子树所有节点值,中序遍历结果为升序序列。
- 前驱节点的定位逻辑,涉及树结构的遍历与条件判断。
解题过程循序渐进讲解
第一步:理解中序遍历顺序与前驱定义
- 回顾二叉搜索树的中序遍历:左子树 → 根节点 → 右子树,结果是节点值升序排列。
- 对于节点
p,其前驱节点就是中序遍历顺序中紧邻在p前面的节点。 - 示例:树
[2,1,3]中序遍历为[1,2,3]。- 节点
2的前驱是1。 - 节点
1是第一个节点,没有前驱。 - 节点
3的前驱是2。
- 节点
第二步:分析前驱节点的两种可能情况
假设我们要找节点 p 的前驱:
- 情况一:
p有左子树。- 前驱是
p左子树中的最大节点(即左子树的最右节点)。 - 原因:中序遍历时,遍历完左子树的最大节点后,下一个就访问
p。
- 前驱是
- 情况二:
p没有左子树。- 前驱是
p的祖先节点中,第一个“作为右子树的祖先”的父节点。 - 更准确:从根向
p走,记录最后一个“拐向右边”的节点。 - 原因:如果
p是父节点的右孩子,则父节点是前驱(因为父节点比p小,且中间没有其他节点);如果p是父节点的左孩子,则继续向上找,直到某个节点是其父节点的右孩子(该父节点是前驱),如果直到根都没有,说明p是最小节点,无前驱。
- 前驱是
第三步:用算法步骤描述
给定 BST 的根节点 root 和目标节点 p:
- 如果
p.left != null:- 进入
p.left,一直向右走到尽头,返回该节点。
- 进入
- 否则(
p.left == null):- 从根开始遍历,用变量
pred记录前驱(初始为null)。 - 比较当前节点
curr与p.val:- 如果
curr.val > p.val:p在左子树,curr不可能是前驱(因为比p大),移动到curr.left。 - 如果
curr.val < p.val:curr可能是前驱(比p小),更新pred = curr,然后移动到curr.right。 - 如果
curr == p:找到p,结束循环,返回pred。
- 如果
- 从根开始遍历,用变量
- 返回
pred。
第四步:举例演示
树结构(括号中为值):
6
/ \
2 8
/ \
1 4
/ \
3 5
中序遍历:[1,2,3,4,5,6,8]。
例1:找节点 4 的前驱。
- 它有左子树(节点
3),进入左子树,一直向右:左子树根3无右子,所以前驱是3。 ✅
例2:找节点 5 的前驱。
- 无左子树,从根开始:
- 根
6>5,走左子2。 2<5,更新pred = 2,走右子4。4<5,更新pred = 4,走右子5。- 遇到
5,结束,返回pred = 4。 ✅
- 根
例3:找节点 1 的前驱。
- 无左子树,从根开始:
- 根
6>1,走左子2。 2>1,走左子1。- 遇到
1,此时pred仍为初始null,返回null。 ✅
- 根
第五步:代码实现(迭代法)
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def inorder_predecessor(root, p):
# 情况1:p有左子树
if p.left:
curr = p.left
while curr.right:
curr = curr.right
return curr
# 情况2:p无左子树
pred = None
curr = root
while curr != p:
if curr.val < p.val:
pred = curr
curr = curr.right
else: # curr.val > p.val
curr = curr.left
return pred
时间复杂度:O(h),h 是树高,最好 O(log n)(平衡时),最坏 O(n)(链状)。
空间复杂度:O(1),只用了指针。
第六步:扩展思考
- 如果 BST 节点有指向父节点的指针,可以从
p向上找,代码会更简单。 - 与“中序后继”对称,后继是右子树的最小节点,或无右子树时第一个“作为左子树的祖先”的父节点。
- 在平衡 BST(如 AVL 树、红黑树)中,h = O(log n),效率高。