线段树
一、线段树的定义
线段树是一种二叉搜索树,它将一个区间划分为若干个子区间,每个叶子节点对应原区间的一个单个元素,非叶子节点对应原区间的一个子区间。
线段树的主要作用是高效处理区间查询 和单点/区间更新操作,常见的应用场景包括区间最大值、最小值、区间和的查询与更新等。
资料:https://pan.quark.cn/s/43d906ddfa1b、https://pan.quark.cn/s/90ad8fba8347、https://pan.quark.cn/s/d9d72152d3cf
二、线段树的基本结构
-
节点含义
- 每个节点存储对应区间的信息,例如区间和、区间最大值等。
- 若一个节点对应的区间是
[l, r],且l != r,则该节点会被划分为左右两个子节点,分别对应区间[l, mid]和[mid+1, r],其中mid = (l + r) / 2。 - 叶子节点对应的区间满足
l = r,存储的是原数组中单个元素的值。
-
树的形态
- 线段树是一棵近似完全二叉树,对于长度为
n的原数组,线段树的节点总数约为4 * n,因此在实现时通常会开辟4 * n大小的数组来存储线段树。
- 线段树是一棵近似完全二叉树,对于长度为
三、线段树的核心操作
1. 构建(Build)
构建操作是将原数组的信息填充到线段树的各个节点中,自底向上进行。
- 递归终止条件:当
l == r时,当前节点的值等于原数组中下标为l的元素值。 - 递归过程:先递归构建左子树和右子树,再根据左右子节点的值计算当前节点的值。
2. 单点更新(Point Update)
单点更新是修改原数组中某个位置的元素值,并更新线段树中相关的节点。
- 递归终止条件:当
l == r时,修改当前节点的值。 - 递归过程:根据要更新的位置,判断是在左子树还是右子树,递归更新对应子树,然后回溯更新当前节点的值。
3. 区间查询(Range Query)
区间查询是查询原数组中某个区间 [ql, qr] 的统计信息,例如区间和、区间最大值等。
- 递归终止条件:如果当前节点对应的区间
[l, r]完全在查询区间[ql, qr]内,直接返回当前节点的值。 - 递归过程:如果当前节点的区间与查询区间无交集,返回无效值;否则递归查询左右子树,合并左右子树的查询结果并返回。
四、线段树的时间复杂度
- 构建操作:时间复杂度为
O(n),需要遍历所有节点一次。 - 单点更新操作:时间复杂度为
O(log n),每次更新最多需要遍历树的高度次节点。 - 区间查询操作:时间复杂度为
O(log n),每次查询最多需要遍历树的高度次节点。
五、线段树的实现示例 以区间和为例
python
class SegmentTree:
def __init__(self, data):
self.n = len(data)
self.tree = [0] * (4 * self.n)
self.build(0, 0, self.n - 1, data)
def build(self, node, l, r, data):
if l == r:
self.tree[node] = data[l]
return
mid = (l + r) // 2
left_node = 2 * node + 1
right_node = 2 * node + 2
self.build(left_node, l, mid, data)
self.build(right_node, mid + 1, r, data)
self.tree[node] = self.tree[left_node] + self.tree[right_node]
def update_point(self, idx, val):
self._update(0, 0, self.n - 1, idx, val)
def _update(self, node, l, r, idx, val):
if l == r:
self.tree[node] = val
return
mid = (l + r) // 2
left_node = 2 * node + 1
right_node = 2 * node + 2
if idx <= mid:
self._update(left_node, l, mid, idx, val)
else:
self._update(right_node, mid + 1, r, idx, val)
self.tree[node] = self.tree[left_node] + self.tree[right_node]
def query_range(self, ql, qr):
return self._query(0, 0, self.n - 1, ql, qr)
def _query(self, node, l, r, ql, qr):
if r < ql or l > qr:
return 0
if ql <= l and r <= qr:
return self.tree[node]
mid = (l + r) // 2
left_node = 2 * node + 1
right_node = 2 * node + 2
left_sum = self._query(left_node, l, mid, ql, qr)
right_sum = self._query(right_node, mid + 1, r, ql, qr)
return left_sum + right_sum
六、线段树的应用场景
- 区间统计类问题:如区间和、区间最大值、区间最小值、区间最大公约数等查询与更新。
- 离线与在线问题:线段树可以处理在线的动态更新和查询操作,相较于前缀和等静态方法更灵活。
- 复杂区间操作:结合懒标记可以处理区间加、区间乘等区间更新操作。
七、扩展 懒标记
当需要进行区间更新操作时,直接递归更新每个节点会导致时间复杂度升高,此时可以引入懒标记 (延迟标记)。
懒标记的核心思想是:当更新的区间完全覆盖当前节点的区间时,先记录更新操作,不立即向下更新子节点,等到后续查询或更新操作需要访问子节点时,再将标记下传,完成子节点的更新。