主要的作用是求解数组的 区间和 和 修改数组元素
以往主要是利用两种算法:1.朴素算法 2.前缀和
这里把求区间和叫做query
修改数组元素叫update
1.朴素算法
在执行update
的时候时间复杂度O(1)
执行query
时候时间见复杂度O(n)
2.前缀和
在执行update
的时候时间复杂度O(n)
执行query
时候时间见复杂度O(1)
上面的两种算法在都有各自的有点,但是在同时处理两个问题的时候,就显得有些吃力了,O(n)
的复杂度有很大的限制。解决方法就是利用线段树这种数据结构,它可以把两种操作的时间复杂度降到O(logn)
;
线段树
先补充一个前备的知识点:假设有一树状数组tree[MAX_LEN]
,对于某个节点node
有
左
儿
子
:
l
e
f
t
n
o
d
e
[
i
n
d
e
x
]
,
i
n
d
e
x
=
2
∗
n
o
d
e
+
1
左儿子:leftnode[index],index=2*node + 1
左儿子:leftnode[index],index=2∗node+1
右
儿
子
:
r
i
g
h
t
n
o
d
e
[
i
n
d
e
x
]
,
i
n
d
e
x
=
2
∗
n
o
d
e
+
2
右儿子:rightnode[index],index=2*node + 2
右儿子:rightnode[index],index=2∗node+2
前提有这样一个数组int arr[10] = {1, 3, 5, 7, 9, 11};
根据已有数组建树
这个过程类似于堆排序那样,所有的叶节点保存的是arr[]
数组的每一个元素,中间节点保存的是以这个节点为根节点的树的值;
const int MAX_LEN = 1000;
int len = 6;
int tree[MAX_LEN];
int arr[10] = {1, 3, 5, 7, 9, 11};
void build_tree(int *arr, int *tree, int node, int star, int end)
{
if (star == end)
{
tree[node] = arr[star];
// 递归的出口,这时的 node 相当于这个树的叶节点
}
else
{
int mid = (star + end) / 2;
int left_node = (2 * node) + 1;
int right_node = (2 * node) + 2;
build_tree(arr, tree, left_node, star, mid);
build_tree(arr, tree, right_node, mid + 1, end);
tree[node] = tree[left_node] + tree[right_node];
}
}
就会按这种格式建立一个树;
update_num
修改arr[]
数组中指定位置元素的值,对给定index
value
使arr[index]=value
void update_num(int *arr, int *tree, int node, int star, int end, int index, int value)
{
if (star == end)
{
arr[index] = value;
tree[node] = value;
}
else
{
int mid = (star + end) / 2;
int left_node = (2 * node) + 1;
int right_node = (2 * node) + 2;
if (index >= star && index <= mid)
{
update_num(arr, tree, left_node, star, mid, index, value);
}
else
{
update_num(arr, tree, right_node, mid + 1, end, index, value);
}
tree[node] = tree[left_node] + tree[right_node];
// 回溯修改各个节点的值;
}
}
这个过程和上面的建树过程很相似,建树是不断的把,这个arr[]
左右分开递归调用建立起来的;修改不断的把左右区间的范围和index
比较直到找到这个位置就修改,然后用同样的方法回溯修改各个父节点的值;
query_sum
对于给定的左右边界求arr[]
在这个区间的和
int query_sum(int *arr, int *tree, int node, int star, int end, int L, int R)
{
cout << "star = " << star << " end = " << end << endl;
// 在递归过程中如果这个区间不在这个要求的区间内就可以直接返回0
if (L > end || R < star)
{
return 0;
}// 找到单个节点就返回
else if (star == end)
{
return tree[node];
}// 找到区间值,返回
else if (star >= L && end <= R)
{
return tree[node];
}
else
{
int mid = (star + end) / 2;
int left_node = (2 * node) + 1;
int right_node = (2 * node) + 2;
int left_sum = query_sum(arr, tree, left_node, star, mid, L, R);
int right_sum = query_sum(arr, tree, right_node, mid + 1, end, L, R);
return left_sum + right_sum;
}
}
区间修改(update_section
)
不会!!!!
区间最大值
有了上面的基础求区间最大值就变的简单了,在求区间和的时候,各个节点上保存的是,以这个结点作为根节点的树的和,改为保存这个区间的最大值就好了; 单点修改时,记得要把最后回溯的过程也改为求左右儿子的最大值
const int MAX_LEN = 1000;
int len = 6;
int tree[MAX_LEN];
int arr[10] = {1, 3, 5, 7, 9, 11};
void build_tree(int *arr, int *tree, int node, int star, int end)
{
if (star == end)
{
tree[node] = arr[star];
// 递归的出口,这时的 node 相当于这个树的叶节点
}
else
{
int mid = (star + end) / 2;
int left_node = (2 * node) + 1;
int right_node = (2 * node) + 2;
build_tree(arr, tree, left_node, star, mid);
build_tree(arr, tree, right_node, mid + 1, end);
tree[node] = max(tree[left_node], tree[right_node]);//比较左右儿子保存最大值
}
}