线段树
线段树是一种基于分治思想的二叉树结构,用于高效处理区间或线段的查询与更新操作。
提示
线段树擅长处理动态的顺序统计问题,特别是在动态变化的空间中快速找到第 k 个空位。
线段树的本质是将区间划分为左右两个区间,递归求解。整个线段被划分为树结构,如下图:
该线段树基于数组 [1, 2, 3, 4, 5, 6, 7, 8] 构建,每个节点存储对应区间的和。建树过程通常采用递归自顶向下的方式,其节点编号与区间划分遵循以下规律:
- 对于节点 ,其左儿子节点为 ,右儿子节点为 。
- 若 表示区间 (即 ),则左儿子表示区间 ,右儿子表示区间 。
在实现时,采用递归建树:
设当前根节点为 ,若其管辖的区间长度已经是 (当前节点对应的区间只有一个元素),则直接根据 数组上相应位置的值初始化该节点(叶子节点存储原数组中该位置的值);否则将区间从中点分割为两个子区间,分别进入左右子节点递归建树,最后合并两个子节点的信息。
具体建树过程如下:
- 根节点:区间 ,其值等于左子区间 与右子区间 的和。
- 递归左子树:
- 节点 的值等于 与 的和。
- 节点 的值等于 与 的和,叶子节点 存储 , 存储 ,得 。
- 节点 的值等于 与 的和,叶子 存储 , 存储 ,得 。
- 因此 。
- 递归右子树:
- 节点 的值等于 与 的和。
- 节点 的值等于 与 的和,叶子 存储 , 存储 ,得 。
- 节点 的值等于 与 的和,叶子 存储 , 存储 ,得 。
- 因此 。
- 根节点值:。
线段树的高度是(若根节点高度为1),其中是原数组的长度。
线段树是一棵平衡二叉树,其叶子节点对应原数组的每个元素。为了便于数组存储,通常将其构建为一棵完全二叉树(或近似完全二叉树)。对于长度为 的数组,叶子节点个数为 ,但为了满足完全二叉树的结构,实际叶子节点数会向上取整到 。
- 若根节点高度为 ,则树的高度 。
- 若根节点高度为 ,则 。
看两个例子:
- :,高度 (根高度 )或 (根高度 )。
- :,高度同样为 (根高度 ),因为需要补齐到 个叶子。
在线段树的数组实现中,通常开辟 大小的空间,正是基于这个高度()来保证不越界。
因此,线段树的高度是 级别的。
建树代码:
void build(int p, int l, int r) {
// p: 当前节点在数组中的下标。
// l: 当前节点所管辖区间的左端点(包含),对应原数组 `a` 的下标。
// r: 当前节点所管辖区间的右端点(包含),对应原数组 `a` 的下标。
if (l == r) {// 递归基:区间只有一个元素
tree[p] = a[l];// 叶子节点直接存储原数组该位置的值
return;// 结束当前分支的递归
}
int mid = (l + r) / 2;// 计算当前区间的中点,将区间分成 [l, mid] 和 [mid+1, r] 两半
build(p * 2, l, mid);// 递归构建左子节点,左子节点下标为 p*2,区间为 [l, mid]
build(p * 2 + 1, mid + 1, r);// 递归构建右子节点,右子节点下标为 p*2+1,区间为 [mid+1, r]
tree[p] = tree[p * 2] + tree[p * 2 + 1];// 回溯时合并左右子树的信息,这里以“和”为例
}