经典场景:
一个给定较大的数组X:
要求1: 可以随时更新某个区间[i,j]内的元素
要求2:可以查询火统计某个区间[i,j]内最值/和等。
常见做法:
对于要求1,遍历区间[i, j],进行元素的更新,时间复杂度 O ( n ) ;
对于要求2,遍历区间[i, j],查询最值,时间复杂度 O ( n ) ;
如数组元素个数较大时,耗时严重。由此引入线段树,保证对数数据内更新火查询某个区间。
线段树定义:
线段树是一种利用分治思想处理对一段序列进行大量区间操作(修改、查询)的数据结构。线段树的叶节点即为原序列,内部节点除了左右儿子之外,还保存着其分管的某段区间信息,如区间和、区间最值。显然,每个节点所分管的区间即为以其为根的子树中所有叶节点。
预处理生成线段树:耗时O ( n )
空间复杂度O ( nlogn )
区间更新时间复杂度O (log n )
区间查询时间复杂度O (log n )
假设数组 x
为:x = [ 2, 5, 1, 4, 9, 3 ]
,一共 n=6
个元素,则构建的线段数如下
意构建的时候,在非叶子节点更新你想要存储的区间信息(区间最大值,区间最小值等,这里选择最小值)
区间查询
查询[1,4]内的最小值
1 找到区间范围内的最下曾节点(不一定是叶子)
图中标记 *
号的节点为当前需要查询的节点,注意区间[3-4]不需要查询叶子,本身已经是子区间范围内最值。故[1, 4]
区间包含的节点是:1-1, 2-2, 3-4
2 递归向上查询最小值
对应的最小值分别x[1-1]=5,x[2,2]=1,x[3,4]=4
min(x14) = min(x12, x34)
= min(min(x11, x22), 4)
= min(min(5, 1), 4)
= min(1, 4)
= 1
区间更新
[1, 4]
之间所有元素加 3
1 找到需要更新的最下层节点(类似的,也不一定是叶子)
图中标记 *
号的节点为当前需要更新的节点,类似的,故[1, 4]
区间包含的节点是:1-1, 2-2, 3-4
2 递归向上更新区间
图中标记 *
或 #
号的节点为更新过程中需要更新最小值的节点。[3, 4]
完全处于 [1, 4]
的更新范围内,在更新时无需更新其所有子节点。但此时但是按照上图的结果,如果我进行查询 [3, 3]
的最小值(或者更新值)。显然结果与图示的数据不符合([3,3]最小值是4但是区间[3-4]显示最小值是7,矛盾!!!)。
所以需要增加更新标志位,处理上面所提及的情况
该标志位记录当前区间的变更值。方便后续在进行涉及该区间的子节点查询 / 更新时,需要取出该值递归更新沿途的区间。
如后续查询 [3, 3]时,
其中 *
号为更新后的值,#
号为传递的标志位。在更新了区间后,需要把原有的 [3, 4]
标志位清除(否则下次会导致重复更新)。
在此基础尚,需区间 [4, 5]
所有元素 减去2
把区间标志位和变更值一起传递(注意区间范围),同时清除已经更新完的区间标志位。
最终结果
代码实现:
线段树是一种二叉树,当然可以像一般的树那样写成结构体,也可以用数组来实现树形结构,可以大大简化代码。
假设某个节点的编号为v,那么它的左子节点编号为2*v,右子节点编号为2*v+1。
然后规定根节点为1.这样一颗二叉树就构造完成了。通常2*v在代码中写成 v<<1 。 2*v+1写成 v<<1|1
更详细参考这里