基于线段树的数据结构

发布时间:2026/7/31 12:23:32
基于线段树的数据结构 基于线段树的数据结构什么是线段树线段树是一种用于解决区间查询和区间更新问题的二叉树数据结构。它像一棵魔法树能把一个数组分成多个区间让我们能高效地处理各种区间操作。比如你想知道数组里第3到第7个元素的和或者想把这个范围内的所有数都加上5线段树就是你的得力助手。想象你有一个长长的书架上面摆满了书。如果你想统计从第5格到第12格有多少本书传统方法得一本本数效率很低。而线段树就像给这个书架建了一个索引系统每个格子对应一个节点节点里存储了该区间内的信息比如总和、最大值、最小值。这样你只需走几个节点就能得到答案速度飞快线段树的典型应用场景包括- 区间求和如计算股票收益- 区间最大值/最小值如监控温度变化- 区间更新如批量修改数据- 区间覆盖如程序性能分析## 线段树的核心原理### 基本结构线段树是一棵完全二叉树每个节点代表一个区间。根节点代表整个数组区间 [0, n-1]左孩子代表左半区间右孩子代表右半区间直到叶子节点代表单个元素。例如数组 [1, 3, 5, 7, 9, 11] 的线段树结构如下[0,5] sum36 / \ [0,2] sum9 [3,5] sum27 / \ / \ [0,1] sum4 [2,2] sum5 [3,4] sum16 [5,5] sum11 / \ / \[0,0] [1,1] [3,3] [4,4]sum1 sum3 sum7 sum9### 存储方式我们通常用数组来存储线段树。对于一个有 n 个元素的数组线段树需要大约 4n 的空间因为完全二叉树节点数约为 2^ceil(log2(n)) * 2 - 1取4倍空间保证安全。## 代码实现### 1. 构建线段树区间求和下面是一个完整的 Python 实现包含构建、查询和更新功能。pythonclass SegmentTree: def __init__(self, data): 初始化线段树 data: 原始数组 self.n len(data) self.tree [0] * (4 * self.n) # 分配4倍空间 self._build(data, 0, 0, self.n - 1) def _build(self, data, node, start, end): 递归构建线段树 node: 当前节点在tree数组中的索引 start, end: 当前节点代表的区间范围 if start end: # 叶子节点存储单个元素 self.tree[node] data[start] else: mid (start end) // 2 left_child 2 * node 1 right_child 2 * node 2 # 递归构建左右子树 self._build(data, left_child, start, mid) self._build(data, right_child, mid 1, end) # 当前节点的值 左孩子 右孩子 self.tree[node] self.tree[left_child] self.tree[right_child] def query(self, L, R): 区间查询求[L, R]的和 L, R: 查询区间的左右边界闭区间 return self._query(0, 0, self.n - 1, L, R) def _query(self, node, start, end, L, R): 递归查询区间和 if R start or L end: # 查询区间与当前节点区间无交集 return 0 if L start and end R: # 当前节点完全在查询区间内 return self.tree[node] # 部分重叠递归查询左右子树 mid (start end) // 2 left_sum self._query(2 * node 1, start, mid, L, R) right_sum self._query(2 * node 2, mid 1, end, L, R) return left_sum right_sum def update(self, idx, value): 单点更新将索引idx的元素更新为value self._update(0, 0, self.n - 1, idx, value) def _update(self, node, start, end, idx, value): 递归更新单个位置的值 if start end: # 找到目标叶子节点 self.tree[node] value else: mid (start end) // 2 if idx mid: self._update(2 * node 1, start, mid, idx, value) else: self._update(2 * node 2, mid 1, end, idx, value) # 更新当前节点为左右孩子之和 self.tree[node] self.tree[2 * node 1] self.tree[2 * node 2]# 测试代码if __name__ __main__: arr [1, 3, 5, 7, 9, 11] seg SegmentTree(arr) print(原始数组:, arr) print(查询 [1, 4] 的和:, seg.query(1, 4)) # 3579 24 seg.update(2, 10) # 将索引2的元素从5改为10 print(更新后数组:, arr) # 注意这里arr不变因为线段树内部存储独立 print(更新后查询 [1, 4] 的和:, seg.query(1, 4)) # 31079 29### 2. 区间最大值线段树有时候我们需要快速知道某段区间的最大值比如监控某个时间段内的最高温度。下面是一个求区间最大值的线段树实现。pythonclass MaxSegmentTree: def __init__(self, data): self.n len(data) self.tree [-float(inf)] * (4 * self.n) # 初始化为负无穷 self._build(data, 0, 0, self.n - 1) def _build(self, data, node, start, end): if start end: self.tree[node] data[start] else: mid (start end) // 2 left_child 2 * node 1 right_child 2 * node 2 self._build(data, left_child, start, mid) self._build(data, right_child, mid 1, end) # 当前节点存储左右孩子的最大值 self.tree[node] max(self.tree[left_child], self.tree[right_child]) def query(self, L, R): 查询[L, R]区间的最大值 return self._query(0, 0, self.n - 1, L, R) def _query(self, node, start, end, L, R): if R start or L end: return -float(inf) # 无交集返回负无穷 if L start and end R: return self.tree[node] mid (start end) // 2 left_max self._query(2 * node 1, start, mid, L, R) right_max self._query(2 * node 2, mid 1, end, L, R) return max(left_max, right_max) def update(self, idx, value): 单点更新 self._update(0, 0, self.n - 1, idx, value) def _update(self, node, start, end, idx, value): if start end: self.tree[node] value else: mid (start end) // 2 if idx mid: self._update(2 * node 1, start, mid, idx, value) else: self._update(2 * node 2, mid 1, end, idx, value) self.tree[node] max(self.tree[2 * node 1], self.tree[2 * node 2])# 测试代码if __name__ __main__: temperatures [28, 31, 25, 33, 27, 30] max_tree MaxSegmentTree(temperatures) print(温度数据:, temperatures) print(第0到第3天的最高温度:, max_tree.query(0, 3)) # max(28,31,25,33) 33 max_tree.update(3, 35) # 第3天温度变为35度 print(更新后第0到第3天的最高温度:, max_tree.query(0, 3)) # max(28,31,25,35) 35## 线段树的复杂度分析-构建线段树O(n)因为每个节点只被访问一次。-区间查询O(log n)每次查询只需要遍历树的一条路径最多4*log(n)个节点。-单点更新O(log n)只需要更新从根到叶子的路径上的节点。相比之下暴力法的查询和更新都是 O(n)当数据量很大时比如 n10^5查询次数 m10^5暴力法需要 10^10 次操作而线段树只需要约 10^5 * log(10^5) ≈ 1.7×10^6 次操作快了近6000倍## 线段树的进阶应用1.懒惰传播Lazy Propagation当需要频繁进行区间更新如给整个区间加一个值时懒惰传播可以避免每次都更新到叶子节点大大提升效率。2.可持久化线段树允许我们回溯到历史版本常用于解决区间第K大等问题。3.线段树合并将两棵线段树合并成一棵用于处理树上的问题。4.二维线段树把一维扩展到二维解决矩形区域的查询和更新问题。## 总结线段树是一个强大而优雅的数据结构它巧妙地利用树形结构将区间操作的时间复杂度从 O(n) 降低到 O(log n)。虽然它的实现比暴力法复杂一些但在处理大规模数据时带来的性能提升是巨大的。学习线段树的关键在于理解其分治思想将大问题分解成小问题递归求解再合并结果。这种思想不仅仅适用于区间求和、最值查询还可以扩展到很多其他领域如区间最大子段和、区间异或值等。当你下次遇到需要频繁进行区间操作的场景时不妨想想线段树——它会是你的得力助手。从构建到查询从更新到优化每一条递归路径都体现了计算机科学中的优雅与效率。