线段树详解
线段树详解
线段树(Segment Tree)是一种基于分治思想的高级数据结构,主要用于解决区间查询和区间更新问题。它能够在 O(log n) 的时间内完成区间查询(如区间求和、区间最大值等)和单点/区间更新操作,适用于处理动态区间问题。
1. 线段树的核心思想
线段树的核心思想是将一个区间递归地分成若干个子区间,每个子区间对应线段树中的一个节点。通过预处理和存储这些区间的信息,线段树可以高效地回答区间查询和更新操作。
2. 线段树的基本性质
- 区间划分:
- 线段树的每个节点代表一个区间。
- 根节点代表整个区间
[1, n]
。 - 每个非叶子节点
[l, r]
会被划分为两个子区间:[l, mid]
和[mid+1, r]
,其中mid = (l + r) / 2
。
- 存储信息:
- 每个节点存储其对应区间的某种信息(如区间和、区间最大值等)。
- 递归构建:
- 线段树通过递归方式构建,叶子节点存储单个元素的值,非叶子节点存储其子节点的合并信息。
3. 线段树的实现
3.1 线段树的存储
线段树通常用数组实现,类似于堆的存储方式:
- 根节点下标为
1
。 - 对于节点
i
:- 左子节点下标为
2 * i
。 - 右子节点下标为
2 * i + 1
。
- 左子节点下标为
3.2 线段树的构建
以区间求和为例,构建线段树的步骤如下:
- 递归划分区间:
- 从根节点开始,递归地将区间划分为左右子区间。
- 合并信息:
- 非叶子节点的值为其左右子节点的值之和。
def build_tree(arr, tree, node, start, end):
if start == end:
# 叶子节点,存储单个元素的值
tree[node] = arr[start]
else:
mid = (start + end) // 2
# 递归构建左子树
build_tree(arr, tree, 2 * node, start, mid)
# 递归构建右子树
build_tree(arr, tree, 2 * node + 1, mid + 1, end)
# 合并左右子树的信息
tree[node] = tree[2 * node] + tree[2 * node + 1]
3.3 区间查询
查询区间 [l, r]
的和:
- 如果当前节点区间完全包含在查询区间内,直接返回当前节点的值。
- 如果当前节点区间与查询区间无交集,返回
0
。 - 否则,递归查询左右子区间,并合并结果。
def query_tree(tree, node, start, end, l, r):
if r < start or l > end:
# 当前区间与查询区间无交集
return 0
if l <= start and end <= r:
# 当前区间完全包含在查询区间内
return tree[node]
# 递归查询左右子区间
mid = (start + end) // 2
left_sum = query_tree(tree, 2 * node, start, mid, l, r)
right_sum = query_tree(tree, 2 * node + 1, mid + 1, end, l, r)
return left_sum + right_sum
3.4 单点更新
更新某个位置的值:
- 递归找到对应的叶子节点。
- 更新叶子节点的值,并向上更新其父节点的值。
def update_tree(tree, node, start, end, idx, value):
if start == end:
# 找到目标叶子节点,更新值
tree[node] = value
else:
mid = (start + end) // 2
if idx <= mid:
# 递归更新左子树
update_tree(tree, 2 * node, start, mid, idx, value)
else:
# 递归更新右子树
update_tree(tree, 2 * node + 1, mid + 1, end, idx, value)
# 更新父节点的值
tree[node] = tree[2 * node] + tree[2 * node + 1]
3.5 区间更新(延迟标记)
区间更新可以通过 延迟标记(Lazy Propagation) 优化:
- 在更新时,只更新当前节点,并将更新信息存储在延迟标记中。
- 在查询时,根据需要将延迟标记下推。
4. 线段树的应用场景
- 区间求和:
- 支持区间求和和单点/区间更新。
- 区间最值:
- 支持查询区间最大值或最小值。
- 区间覆盖:
- 支持区间赋值操作。
- 动态区间问题:
- 如区间内满足某种条件的元素个数。
5. 线段树的复杂度分析
- 时间复杂度:
- 构建:O(n)
- 查询:O(log n)
- 更新:O(log n)
- 空间复杂度:
- O(4n)(通常需要 4 倍于原数组的空间)。
6. 代码示例
以下是一个完整的线段树实现(区间求和):
class SegmentTree:
def __init__(self, arr):
self.n = len(arr)
self.tree = [0] * (4 * self.n)
self.build(arr, 1, 0, self.n - 1)
def build(self, arr, node, start, end):
if start == end:
self.tree[node] = arr[start]
else:
mid = (start + end) // 2
self.build(arr, 2 * node, start, mid)
self.build(arr, 2 * node + 1, mid + 1, end)
self.tree[node] = self.tree[2 * node] + self.tree[2 * node + 1]
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 = self.query(2 * node, start, mid, l, r)
right = self.query(2 * node + 1, mid + 1, end, l, r)
return left + right
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, start, mid, idx, value)
else:
self.update(2 * node + 1, mid + 1, end, idx, value)
self.tree[node] = self.tree[2 * node] + self.tree[2 * node + 1]
# 示例用法
arr = [1, 3, 5, 7, 9, 11]
st = SegmentTree(arr)
print(st.query(1, 0, len(arr) - 1, 1, 3)) # 输出 15 (3 + 5 + 7)
st.update(1, 0, len(arr) - 1, 2, 10) # 将 arr[2] 更新为 10
print(st.query(1, 0, len(arr) - 1, 1, 3)) # 输出 20 (3 + 10 + 7)
7. 总结
线段树是一种强大的数据结构,适用于解决动态区间问题。通过递归划分区间和合并信息,线段树能够高效地支持区间查询和更新操作。掌握线段树的实现和应用,可以极大地提升解决复杂问题的能力。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)