递归线段树是否比迭代线段树占用更多空间?实现疑问
关于线段树2n空间递归实现的疑问
我正在学习线段树(segment tree)数据结构。见过不少仅用2n空间的迭代式线段树,于是尝试把同样的构建方法用到带递归update和sumRange的线段树上,但没能成功。想请教:
- 这种做法是否不可行?
- 为什么迭代式线段树能用2n空间存储,递归式却需要4n?
- 还是我的实现有缺陷?
我尝试的2n空间版本用的是1-indexed树,tree[0]不存数据,根节点在tree[1]。递归调用时初始范围设为1到n-1,但不确定对不对:改成到self.n、从0开始,或是传递index+1、left+1、right+1时,都会出现不同的错误结果。
尝试的2n空间递归实现
from typing import List class NumArray: # Classic Segment Tree def __init__(self, nums: List[int]): self.n = len(nums) self.tree = [0] * self.n * 2 self.build(nums) def build(self, nums): # leaves for i in range(self.n): self.tree[i + self.n] = nums[i] # internal for i in range(self.n - 1, 0, -1): self.tree[i] = self.tree[i * 2] + self.tree[i * 2 + 1] def merge(self, left, right): return left + right def _update(self, tree_idx, seg_left, seg_right, i, val): # leaf if seg_left == seg_right: self.tree[tree_idx] = val return mid = (seg_left + seg_right) // 2 if i > mid: self._update(tree_idx * 2 + 1, mid + 1, seg_right, i, val) else: self._update(tree_idx * 2, seg_left, mid, i, val) self.tree[tree_idx] = self.merge(self.tree[tree_idx * 2], self.tree[tree_idx * 2 + 1]) def update(self, index: int, val: int) -> None: self._update(1, 1, self.n - 1, index, val) def _sumRange(self, tree_idx, seg_left, seg_right, query_left, query_right): # segment out of query bounds if seg_left > query_right or seg_right < query_left: return 0 # segment fully in bounds if seg_left >= query_left and seg_right <= query_right: return self.tree[tree_idx] # segment partially in bounds mid = (seg_left + seg_right) // 2 # 优化:仅必要时遍历单侧子树 if query_left > mid: return self._sumRange(tree_idx * 2 + 1, mid + 1, seg_right, query_left, query_right) elif query_right <= mid: return self._sumRange(tree_idx * 2, seg_left, mid, query_left, query_right) left_sum = self._sumRange(tree_idx * 2, seg_left, mid, query_left, query_right) right_sum = self._sumRange(tree_idx * 2 + 1, mid + 1, seg_right, query_left, query_right) return self.merge(left_sum, right_sum) def sumRange(self, left: int, right: int) -> int: return self._sumRange(1, 1, self.n - 1, left, right)
可正常运行的4n空间递归实现
from typing import List class NumArray: # Classic Segment Tree # 0-indexed recursive def __init__(self, nums: List[int]): self.n = len(nums) self.tree = [0] * self.n * 4 self.build(nums, 0, 0, self.n - 1) def build(self, nums, tree_idx, left, right): # leaf if left == right: self.tree[tree_idx] = nums[left] return mid = (left + right) // 2 self.build(nums, tree_idx * 2 + 1, left, mid) self.build(nums, tree_idx * 2 + 2, mid + 1, right) self.tree[tree_idx] = self.tree[tree_idx * 2 + 1] + self.tree[tree_idx * 2 + 2] def merge(self, left, right): return left + right def _update(self, tree_idx, seg_left, seg_right, i, val): # leaf if seg_left == seg_right: self.tree[tree_idx] = val return mid = (seg_left + seg_right) // 2 if i > mid: self._update(tree_idx * 2 + 2, mid + 1, seg_right, i, val) else: self._update(tree_idx * 2 + 1, seg_left, mid, i, val) self.tree[tree_idx] = self.merge(self.tree[tree_idx * 2 + 1], self.tree[tree_idx * 2 + 2]) def update(self, index: int, val: int) -> None: self._update(0, 0, self.n - 1, index, val) def _sumRange(self, tree_idx, seg_left, seg_right, query_left, query_right): # segment out of query bounds if seg_left > query_right or seg_right < query_left: return 0 # segment fully in bounds if seg_left >= query_left and seg_right <= query_right: return self.tree[tree_idx] # segment partially in bounds mid = (seg_left + seg_right) // 2 # 优化:仅必要时遍历单侧子树 if query_left > mid: return self._sumRange(tree_idx * 2 + 2, mid + 1, seg_right, query_left, query_right) elif query_right <= mid: return self._sumRange(tree_idx * 2 + 1, seg_left, mid, query_left, query_right) left_sum = self._sumRange(tree_idx * 2 + 1, seg_left, mid, query_left, query_right) right_sum = self._sumRange(tree_idx * 2 + 2, mid + 1, seg_right, query_left, query_right) return self.merge(left_sum, right_sum) def sumRange(self, left: int, right: int) -> int: return self._sumRange(0, 0, self.n - 1, left, right)
问题解答
1. 2n空间的递归线段树完全可行,你的实现存在索引匹配错误
你尝试的版本核心问题是递归时的区间范围和数组索引不匹配:
- 线段树叶子节点
tree[n]到tree[2n-1]对应原数组的nums[0]到nums[n-1](0-indexed); - 但递归调用
update和sumRange时,初始区间设为(1, self.n-1),错误地把原数组当成1-indexed处理,导致索引错位。
修复方法:将递归初始调用的区间改为原数组的0-indexed范围:
def update(self, index: int, val: int) -> None: self._update(1, 0, self.n - 1, index, val) def sumRange(self, left: int, right: int) -> int: return self._sumRange(1, 0, self.n - 1, left, right)
修改后,2n空间的递归实现即可正常工作。
2. 为什么迭代式用2n,传统递归式用4n?
这两种线段树的结构设计不同:
- 2n空间线段树:是固定结构的完全二叉树,叶子节点层直接对应原数组(长度不足2的幂时会补0),父节点可通过编号直接计算子节点(
2*i和2*i+1),空间利用紧凑,天然只需要2n空间,迭代和递归都能复用该结构; - 4n空间线段树:是自适应结构的二叉树,递归构建时根据实际区间划分左右子树,不需要补全到2的幂,为避免数组越界,通常用4n作为保守的安全空间(理论最坏情况需要2n,4n是简化实现的通用选择)。
内容的提问来源于stack exchange,提问作者Alec
相关产品推荐
相关产品推荐

