线段树简介
线段树(Segment Tree)是一种基于分治思想的二叉树数据结构,用于在数组上高效支持区间查询与区间/单点修改。
核心思想:将区间不断二分为左右子区间,每个节点代表一个区间,根节点代表整个数组区间,叶子节点代表单点。通过合并左右子区间的信息得到当前区间的信息,从而在 O(log n) 内完成区间查询与修改。
常见应用:
- 区间和/积:查询与更新区间和、区间乘积
- 区间最值:区间最大值、最小值
- 区间 GCD / 其他可合并信息:满足结合律的运算均可
基本性质与结构
树结构
- 根节点:表示整个数组区间
[0, n-1] - 内部节点:表示某个区间
[l, r],左子表示[l, mid],右子表示[mid+1, r],其中mid = (l+r)/2 - 叶子节点:表示单点区间
[l, l],对应原数组的一个元素
flowchart TB
subgraph 区间 [1, 6]
A["[1,6]"]
end
A --> B["[1,3]"]
A --> C["[4,6]"]
B --> D["[1,2]"]
B --> E["[3,3]"]
C --> F["[4,5]"]
C --> G["[6,6]"]
D --> H["[1,1]"]
D --> I["[2,2]"]
F --> J["[4,4]"]
F --> K["[5,5]"]
存储方式
线段树常用数组存储(堆式存储),下标从 1 开始较方便:
- 节点
i的左子:2*i,右子:2*i+1 - 父节点:
i/2
若区间长度为 n,开 4n 大小的数组即可安全覆盖所有节点。
时间复杂度
| 操作 | 时间复杂度 |
|---|---|
| 建树 | O(n) |
| 单点修改 | O(log n) |
| 单点/区间查询 | O(log n) |
| 区间修改(含懒标记) | O(log n) |
Golang 实现
区间和 + 单点修改
以下实现:维护区间和,支持单点修改与区间和查询。
1 | package main |
区间和 + 区间修改(懒标记)
当需要区间统一加/减时,若每次暴力更新区间内每个叶子,复杂度会退化为 O(n)。使用懒标记(Lazy Propagation):在“需要向下递归”时才把当前节点的修改下推,保证每次修改/查询只经过 O(log n) 层。
1 | // SegmentTreeLazy 带懒标记的区间和线段树(区间加) |
区间最值(以最大值为例)
只需把“合并”从加法改为取 max,单点/区间查询写法类似。
1 | // SegmentTreeMax 区间最大值线段树 |
懒标记小结
flowchart LR
A[区间修改请求] --> B{当前节点区间是否被完全覆盖?}
B -->|是| C[更新当前节点 + 打懒标记]
B -->|否| D[下推懒标记到左右子]
D --> E[递归左右子]
E --> F[合并左右子信息]
要点:
- 修改时:若当前区间被
[ql, qr]完全包含,则只更新当前节点并打懒标记,不继续下推。 - 查询或继续递归前:若需要访问子节点,先对该节点执行
pushDown,再递归。 - 懒标记含义与具体操作一致(如“区间加”则存加数;“区间赋值为 x”可存赋值与长度,下推时覆盖子区间)。
经典例题思路
| 问题 | 维护信息 | 修改方式 |
|---|---|---|
| 区间和 + 单点改 | 和 | 单点 |
| 区间和 + 区间加 | 和 + 懒标记 | 区间加 |
| 区间最值 | max/min | 单点或配合懒标记 |
| 区间 GCD | gcd | 单点 |
| 区间染色/覆盖 | 颜色或覆盖标记 | 区间赋值 + 懒标记 |
与树状数组对比
| 特性 | 线段树 | 树状数组 |
|---|---|---|
| 区间查询 | 支持任意区间 | 通常前缀区间 |
| 区间修改 | 支持(懒标记) | 需差分等技巧 |
| 空间 | 约 4n | 约 n |
| 实现难度 | 稍高 | 较低 |
| 适用 | 区间和/最值/GCD 等 | 前缀和、逆序对等 |
线段树是解决区间可合并信息的通用结构,掌握建树、单点/区间修改与懒标记下推后,即可应对多数区间查询与修改问题。