数据结构 - 线段树 Segment Tree
在许多算法问题中,我们经常需要对数组的一部分进行操作,比如:
- 查询一个数组的某个区间的总和、最大值或最小值;
- 动态更新数组中的某些值,同时保持高效的区间查询;
- 应对动态区间修改和查询问题,例如批量加值或区间替换。
直接使用暴力遍历显然无法满足高效率的需求,特别是当数组规模较大或操作频繁时。这时,线段树(Segment Tree) 就成为了处理这类问题的强大工具。
线段树通过分治思想将数组分解成多个区间块,使得每次查询和更新操作都可以在对数时间内完成。本文将详细介绍线段树的基本概念、操作方法、代码实现及应用场景。
1. 什么是线段树
线段树是一种基于二叉树的数据结构,主要用于解决区间查询和区间修改问题。
- 每个节点表示数组的一个区间;
- 叶子节点表示数组的单个元素;
- 内部节点通过合并子节点的信息,表示更大的区间。
线段树的核心在于:通过递归划分区间,能够快速处理大范围的操作,同时保留局部信息以支持动态更新。
2. 线段树的基本操作
使用数组来存储线段树。对于一个数组 a
,其线段树的根节点存储在数组的下标 1
处,左右子节点分别存储在 2*i
和 2*i+1
处。
2.1 树的构造
假设我们需要支持区间求和操作。线段树的构造递归如下:
- 如果当前区间为单个元素,则直接存储该值;
- 否则,将区间分为左右两部分,递归构造子树,并将子树的值合并存储到当前节点。
int tree[MAX_L];
void init(int node, int[] a, int start, int end) {
if (start == end) {
tree[node] = a[start];
return;
}
int mid = (start + end) / 2;
int lnode = 2 * node;
int rnode = 2 * node + 1;
init(lnode, a, start, mid);
init(rnode, a, mid + 1, end);
tree[node] = tree[lnode] + tree[rnode];
}
2.2 区间查询
假设我们需要查询区间 [L,R]
的和:
- 如果当前区间完全在
[L,R]
范围内,直接返回该区间的值; - 如果当前区间完全不在
[L, R]
范围内,返回 0; - 如果这个区间的左孩子与
[L,R]
有交集,那么搜索左孩子;如果这个区间的右孩子与[L,R]
有交集,那么搜索右孩子。
int query(int node , int start, int end, int L, int R) {
if (start >= L && end <= R)
return tree[node];
if (start > R || end < L)
return 0;
int mid = (start + end) / 2;
int lnode = 2 * node;
int rnode = 2 * node + 1;
int sum = 0;
sum += query(lnode, start, mid, L, R);
sum += query(rnode, mid + 1, end, L, R);
return sum;
}
2.3 单点修改
假设我们需要修改数组的某个位置 index
的值为 newValue
:
- 找到对应的叶子节点,并更新值;
- 回溯更新所有父节点的值。
void update(int node, int start, int end, int index, int newValue) {
if (start == end) {
tree[node] = newValue;
return;
}
int mid = (start + end) / 2;
int lnode = node * 2;
int rnode = node * 2 + 1;
if (index <= mid)
update(lnode, start, mid, index, newValue);
else
update(rnode, mid + 1, end, index, newValue);
tree[node] = tree[lnode] + tree[rnode];
}
3. 线段树的复杂度分析
- 时间复杂度:对于 个元素的数组,二叉树的高度不超过 ,那么树的节点数量不超过 ,因此构造线段树的时间复杂度为 ;区间查询的时间复杂度为 ,单点修改的时间复杂度为 。
- 空间复杂度:。
4. 线段树的模板
class SegmentTree {
public:
void init(int node, std::vector<int>& a, int start, int end) {
if (node == 1) {
n = a.size();
tree = std::vector<int>(n * 2 + 1);
}
if (start == end) {
tree[node] = a[start];
return;
}
int mid = (start + end) / 2;
int lnode = 2 * node;
int rnode = 2 * node + 1;
init(lnode, a, start, mid);
init(rnode, a, mid + 1, end);
tree[node] = tree[lnode] + tree[rnode];
}
int query(int node , int start, int end, int L, int R) {
if (start >= L && end <= R)
return tree[node];
if (start > R || end < L)
return 0;
int mid = (start + end) / 2;
int lnode = 2 * node;
int rnode = 2 * node + 1;
int sum = 0;
sum += query(lnode, start, mid, L, R);
sum += query(rnode, mid + 1, end, L, R);
return sum;
}
void update(int node, int start, int end, int index, int newValue) {
if (start == end) {
tree[node] = newValue;
return;
}
int mid = (start + end) / 2;
int lnode = node * 2;
int rnode = node * 2 + 1;
if (index <= mid)
update(lnode, start, mid, index, newValue);
else
update(rnode, mid + 1, end, index, newValue);
tree[node] = tree[lnode] + tree[rnode];
}
private:
int n;
std::vector<int> tree;
};
5. 实例
本文作者:wenbinteng
本文链接:https://www.cnblogs.com/wenbinteng/p/18734135
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步