线段树(Segment Tree)的区间更新与懒惰传播(Lazy Propagation)优化
字数 1151 2025-11-30 11:09:14
线段树(Segment Tree)的区间更新与懒惰传播(Lazy Propagation)优化
描述
线段树是一种用于高效处理区间查询(如区间和、区间最小值等)的数据结构。当涉及区间更新(如将整个区间内的元素统一加上一个值)时,如果直接递归更新每个叶子节点,时间复杂度会退化为O(n),失去线段树的优势。懒惰传播(Lazy Propagation)是一种优化技术,通过延迟更新操作,将区间更新的时间复杂度优化至O(log n)。
核心思想
懒惰传播的核心是“需要时才更新”。当更新或查询操作覆盖整个区间时,不会立即递归到子节点,而是将更新信息临时存储在当前节点(称为“懒惰标记”),等到后续操作需要访问子节点时,再将标记下推(Push Down)并更新子节点。
步骤详解
-
线段树节点设计
每个节点需要存储:- 区间信息(如区间和
sum)。 - 懒惰标记(如
lazy,表示该区间内所有元素待加的值)。
示例节点结构:
class SegmentTreeNode: def __init__(self, start, end): self.start = start # 区间起始索引 self.end = end # 区间结束索引 self.sum = 0 # 区间和 self.lazy = 0 # 懒惰标记(待加的值) self.left = None self.right = None - 区间信息(如区间和
-
懒惰标记的下推(Push Down)
当需要访问当前节点的子节点时(如查询或更新涉及子区间),必须先将懒惰标记下推:- 将当前节点的
lazy值累加到左右子节点的lazy和sum中。 - 清空当前节点的
lazy。
示例代码:
def push_down(node): if node.lazy != 0: # 更新当前节点的区间和(若区间长度为len,则实际加值为lazy * len) node.sum += node.lazy * (node.end - node.start + 1) # 若非叶子节点,将标记下推 if node.left is not None: node.left.lazy += node.lazy node.right.lazy += node.lazy node.lazy = 0 # 清空标记 - 将当前节点的
-
区间更新操作
- 若当前节点区间完全包含于目标区间,直接更新当前节点:
- 更新
sum:sum += val * (区间长度)。 - 更新
lazy:lazy += val(标记延迟到子节点)。 - 返回(无需继续递归)。
- 更新
- 若当前节点区间与目标区间部分重叠:
- 先执行
push_down,确保子节点数据最新。 - 递归更新左右子节点。
- 更新当前节点的
sum为左右子节点sum之和。
- 先执行
示例代码:
def update(node, l, r, val): if l > node.end or r < node.start: # 无重叠 return if l <= node.start and node.end <= r: # 完全覆盖 node.lazy += val node.sum += val * (node.end - node.start + 1) return push_down(node) # 下推标记 mid = (node.start + node.end) // 2 if l <= mid: update(node.left, l, r, val) if r > mid: update(node.right, l, r, val) node.sum = node.left.sum + node.right.sum # 更新当前节点 - 若当前节点区间完全包含于目标区间,直接更新当前节点:
-
区间查询操作
- 若当前节点区间完全包含于目标区间,直接返回
sum。 - 若部分重叠:
- 先执行
push_down,确保子节点数据最新。 - 递归查询左右子节点并合并结果。
- 先执行
示例代码:
def query(node, l, r): if l > node.end or r < node.start: return 0 if l <= node.start and node.end <= r: return node.sum push_down(node) # 下推标记 return query(node.left, l, r) + query(node.right, l, r) - 若当前节点区间完全包含于目标区间,直接返回
实例演示
假设数组[1, 3, 5, 7, 9],构建线段树后:
- 更新区间
[1, 3](索引从0开始)加2:- 根节点区间
[0,4]部分重叠,下推标记(初始无标记),递归更新左右子节点。 - 左子节点
[0,2]部分重叠,继续下推并递归。 - 右子节点
[3,4]完全包含于[1,3]?否(仅包含索引3),继续递归。 - 最终更新节点
[1,2]和[3,3],并更新懒惰标记。
- 根节点区间
- 查询区间
[0,2]的和时,在访问子节点前下推标记,确保数据正确。
总结
懒惰传播通过延迟更新减少了不必要的递归操作,将区间更新的复杂度优化至O(log n)。关键点在于:
- 懒惰标记记录未传递的更新操作。
- 在访问子节点前必须下推标记。
- 更新和查询操作需协同处理标记。