1. 背景
假设现在有一个非常大的数组 arr,对数组里面的数字需要反复做两个操作:
1)随机的选择一块区间,然后对区间里面的所有数字求和。
2)随机修改数组里面的某一个值,即更新操作。
因为数组是随机存取的,所以更新操作很容易,其时间复杂度是 $O(1)$。
对于求和操作,其时间复杂度取决于区间的长度,假设区间长度为 $L$,则求和操作的时间复杂度为 $O(L)$。
那有没有办法降低求和操作的时间复杂度呢?
首先我们想到的是另外建立一个数组 sumArr,这个数组存储的是 arr 数组的前缀和,即
$$sumArr[i] = sum_{j=0}^{i}arr[j]$$
要求某段的和,只需在 sumArr 数组里找到对应下标做一次减法操作就可以了,时间复杂度变成了 $O(1)$。
这样做的话更新操作的时间复杂度上升了,因为每次更新 arr 数组中的一个元素,都得更新数组 sumArr。
那有没有办法平衡一下求和和更新两个操作的时间复杂度呢?
下面介绍线段树。
2. 线段树
以下面数组进行举例:

线段树本身是一个二叉树,每个结点代表一段区间,结点值就是区间内所有元素的和。
对于线段树中的每一个非叶子节点 $[a,b]$,它的左儿子表示的区间为 $[a,frac{a+b}{2}]$,右儿子表示的区间为 $[frac{a+b}{2}+1,b]$,
因此线段树是平衡二叉树。
针对上面的数组,可以建立如下线段树:

可以看出每个叶子结点就是单个元素。
我们将根节点标号为 0,那么对于任意一个结点 $i$,其左孩子的结点 $id$ 为 $left = 2i + 1$,右孩子的结点 $id$ 为 $right = 2i + 2$。
因为其本身是一个平衡二叉树,所以可以用数组存储,浪费的空间小。
#include <stdio.h>
#define MAX_LEN 1000
void BuildTree(int arr[], int tree[], int node, int start, int end)
{
if(start == end) {
tree[node] = arr[start];
}
else {
int mid = (start + end) / 2;
int leftNode = node * 2 + 1;
int rightNode = node * 2 + 2;
BuildTree(arr, tree, leftNode, start, mid);
BuildTree(arr, tree, rightNode, mid + 1, end);
tree[node] = tree[leftNode] + tree[rightNode];
}
}
void UpdateTree(int arr[], int tree[], int node, int start, int end, int idx, int value)
{
if (start == end) {
arr[idx] = value;
tree[node] = value;
}
else {
int mid = (start + end) / 2;
int leftNode = node * 2 + 1;
int rightNode = node * 2 + 2;
if (idx >= start && idx <= mid) {
UpdateTree(arr, tree, leftNode, start, mid, idx, value);
}
else {
UpdateTree(arr, tree, rightNode, mid + 1, end, idx, value);
}
tree[node] = tree[leftNode] + tree[rightNode];
}
}
int QueryTree(int arr[], int tree[], int node, int start, int end, int L, int R)
{
if (R < start || L > end) return 0;
if (start == end) return tree[node];
if(L <= start && end <= R) return tree[node];
int mid = (start + end) / 2;
int leftNode = node * 2 + 1;
int rightNode = node * 2 + 2;
int sumLeft = QueryTree(arr, tree, leftNode, start, mid, L, R);
int sunRight = QueryTree(arr, tree, rightNode, mid + 1, end, L, R);
return sumLeft + sunRight;
}
int main()
{
int arr[] = {1,3,7,9,11};
int size = 6;
int tree[MAX_LEN] = {0};
BuildTree(arr, tree, 0 , 0, size - 1);
return 0;
}