线段树 (Segment Tree)
Published: Fri Feb 20 2026 | Modified: Sat Feb 07 2026 , 5 minutes reading.
线段树:区间的计算器
算法背后的故事:动态仓库
想象一个有 100 个货架的仓库。每个货架上都有一定数量的箱子。 经理会问两类问题:
- 更新: “5 号货架刚多了 3 个箱子。”
- 查询: “从 10 号货架到 50 号货架总共有多少个箱子?”
- 如果你使用普通数组,更新 很快 (),但 查询 很慢 ( - 你必须走过 40 个货架)。
- 如果你使用预计算的前缀和数组,查询 很快 (),但 更新 很慢 ( - 你必须重新计算 5 号之后的所有和)。
线段树 (Segment Tree) 是中庸之道。它让这两种操作都变得很快 ()。它将货架分组(1-50, 51-100),并分层(1-25, 26-50),然后记住每个组的总和。
为什么需要它?
- 计算几何: 寻找重叠的矩形。
- 分析: “上午 10:00 到下午 2:00 之间的最高股价是多少?”(区间最大值查询)。
- 游戏开发: “在 X 坐标的这个范围内有墙吗?”
算法是如何“思考”的
这个算法是一个分层聚合器。
- 叶子: 原始数组的元素是树的叶子。
- 内部节点: 每个父节点存储其两个孩子的 总和(或最小值/最大值)。
- 查询: 要找到区间 的总和,我们不查看单独的叶子。我们寻找能放入 内的最大的预计算“块”,并将它们加起来。
- 更新: 当叶子发生变化时,我们向上走到根,只更新受影响的父节点。
工程决策:懒惰传播 (Lazy Propagation)
进阶版本使用 懒惰传播。
- 如果你想“给 1 到 50 号货架的每个货架都加 5 个箱子”,一个个加太慢了。
- 相反,你在代表 [1-50] 的节点上贴一张“便利贴”(懒标记),上面写着“+5”。直到有人真正查到它的孩子时,你才去更新它们。这使得区间更新也变成了 。
实现参考 (Python)
class SegmentTree:
def __init__(self, data):
self.n = len(data)
# 树的大小大约是 4*n
self.tree = [0] * (4 * self.n)
self._build(data, 1, 0, self.n - 1)
def _build(self, data, node, start, end):
if start == end:
self.tree[node] = data[start]
else:
mid = (start + end) // 2
self._build(data, 2 * node, start, mid)
self._build(data, 2 * node + 1, mid + 1, end)
# 示例:求和查询
self.tree[node] = self.tree[2 * node] + self.tree[2 * node + 1]
def update(self, idx, val):
self._update(1, 0, self.n - 1, idx, val)
def _update(self, node, start, end, idx, val):
if start == end:
self.tree[node] = val
else:
mid = (start + end) // 2
if start <= idx <= mid:
self._update(2 * node, start, mid, idx, val)
else:
self._update(2 * node + 1, mid + 1, end, idx, val)
self.tree[node] = self.tree[2 * node] + self.tree[2 * node + 1]
def query(self, l, r):
return self._query(1, 0, self.n - 1, l, r)
def _query(self, node, start, end, l, r):
if r < start or end < l:
return 0 # 超出范围
if l <= start and end <= r:
return self.tree[node] # 完全在范围内
mid = (start + end) // 2
p1 = self._query(2 * node, start, mid, l, r)
p2 = self._query(2 * node + 1, mid + 1, end, l, r)
return p1 + p2
# 使用示例
data = [1, 3, 5, 7, 9, 11]
st = SegmentTree(data)
print(f"索引 1 到 3 的和: {st.query(1, 3)}") # 3+5+7 = 15
st.update(1, 10) # 把 3 改成 10
print(f"新和: {st.query(1, 3)}") # 10+5+7 = 22小结
线段树教会我们:聚合能节省时间。通过预先计算群体的摘要,我们无需检查每一个“部分”就能回答关于“整体”的问题。它提醒我们,在数据分析中,缩小视野看到大局的能力与原始数据本身一样重要。
