二叉搜索树(BST)的范围和查询(Range Sum Query)
字数 1603 2025-11-28 12:43:40
二叉搜索树(BST)的范围和查询(Range Sum Query)
问题描述
给定一个二叉搜索树(BST),每个节点包含一个整数值。需要实现一个功能,能够快速计算树中所有值在给定范围[low, high] 内的节点值之和。这个操作可能被多次调用,因此需要一个高效的算法。
BST 特性回顾
二叉搜索树的关键特性是:对于任意节点,其左子树的所有节点值都小于该节点值,其右子树的所有节点值都大于该节点值。这个特性使得我们可以高效地进行有序遍历。
基础解法:中序遍历累加
- 思路:利用BST的中序遍历(左-根-右)会得到一个升序序列的特性。遍历整个树,当遇到节点值在[low, high]范围内时,累加其值。
- 步骤:
- 从根节点开始进行中序遍历(递归或迭代)。
- 对于每个访问的节点:
- 如果节点值小于low,由于BST特性,其左子树的所有节点值都更小,因此可以跳过左子树(实际上中序遍历自然处理,但这里指优化思路:若当前节点已小于low,则其左子树无需再判断,但中序遍历本身会先递归左子树,所以此优化在递归中需在遍历后判断,更佳方案见后续优化)。
- 如果节点值在[low, high]内,将其值加入总和。
- 如果节点值大于high,由于右子树所有节点值都更大,可以停止遍历(因为中序遍历是升序,后续节点值都会大于high)。
- 时间复杂度:最坏情况下(如范围包含所有节点),需要遍历整个树,O(n),其中n是节点数。每次查询都是O(n)。
- 缺点:如果树很大且查询频繁,效率较低。
优化解法:利用BST特性剪枝
- 思路:在中序遍历基础上,根据当前节点值与范围的关系,避免不必要的子树遍历。
- 详细步骤(递归实现):
- 定义递归函数:
rangeSumBST(node, low, high),返回以node为根的子树中值在[low, high]范围内的节点值之和。 - 基准情况:如果node为空,返回0。
- 判断当前节点值(假设为val):
- 如果val < low:说明当前节点及其左子树的值都小于low,因此左子树和当前节点都不在范围内。只需递归计算右子树的范围和。
- 如果val > high:说明当前节点及其右子树的值都大于high,因此右子树和当前节点都不在范围内。只需递归计算左子树的范围和。
- 如果low <= val <= high:当前节点值在范围内,需要累加。同时,左子树中可能有部分节点值在范围内(虽然左子树值都小于val,但可能部分大于等于low),右子树同理(部分可能小于等于high)。因此需要递归计算左右子树的范围和,并加上当前节点值。
- 定义递归函数:
- 时间复杂度:最优情况(范围很小或位于树边缘)可达到O(log n),平均情况优于O(n),因为剪枝避免了全树遍历。最坏情况(范围覆盖整棵树)仍是O(n)。
- 代码示例(递归):
def rangeSumBST(root, low, high): if not root: return 0 if root.val < low: # 当前节点值太小,只遍历右子树 return rangeSumBST(root.right, low, high) elif root.val > high: # 当前节点值太大,只遍历左子树 return rangeSumBST(root.left, low, high) else: # 当前节点在范围内,加上当前值并遍历左右子树 return (root.val + rangeSumBST(root.left, low, high) + rangeSumBST(root.right, low, high))
进一步优化:迭代实现
- 思路:使用栈模拟递归,避免递归函数调用开销,同时保持剪枝逻辑。
- 步骤:
- 初始化栈,将根节点压入栈,总和初始化为0。
- 当栈非空时:
- 弹出当前节点。
- 如果节点为空,跳过。
- 判断节点值:
- 若val < low:仅将右子节点压栈(左子树肯定不在范围)。
- 若val > high:仅将左子节点压栈(右子树肯定不在范围)。
- 若val在范围内:将值加入总和,并将左右子节点都压栈(因为左右子树都可能存在范围内节点)。
- 返回总和。
- 代码示例(迭代):
def rangeSumBST(root, low, high): stack = [root] total = 0 while stack: node = stack.pop() if not node: continue if node.val < low: stack.append(node.right) # 只检查右子树 elif node.val > high: stack.append(node.left) # 只检查左子树 else: total += node.val stack.append(node.left) # 左右子树都需检查 stack.append(node.right) return total
应用场景与扩展
- 实际应用:数据库索引范围查询、统计分数段学生人数等。
- 扩展思考:如果BST经常修改(插入/删除节点),如何支持高效的范围和查询?可结合线段树或树状数组(Fenwick Tree)的思想,但需维护额外信息(如子树和),并在更新时调整这些信息,实现O(log n)的查询和更新。
通过以上步骤,我们理解了BST范围和查询的基础解法、优化策略及实现方式,确保了算法的高效性。