数据结构-Segment Tree 线段树
简介
线段树(Segment Tree)主要用于维护区间信息(要求满足结合律)。与树状数组相比,它可以实现 O(logn)的区间修改,还可以同时支持多种操作(加、乘),更具通用性。
应用
线段树 segmentTree 是一个二叉树,每个结点保存数组 nums 在区间 [s,e] 的最小值、最大值或者总和等信息。线段树可以用树也可以用数组(堆式存储)来实现。对于数组实现,假设根结点的下标为 0,如果一个结点在数组的下标为 node,那么它的左子结点下标为 node×2+1,右子结点下标为 node×2+2。
[s,e] =>[start,end]
创建
线段树的创建通过给定的数组开辟一个四倍数组来存储(参考二叉堆),左子结点下标为 node×2+1,右子结点下标为 node×2+2,递归构建。
1 |
|
关于线段树的空间:如果采用堆式存储( 2p是 p 的左儿子,2p+1 是 p 的右儿子),若有 m 个叶子结点,则 d 数组的范围最大为 。
分析:容易知道线段树的深度是 的,则在堆式储存情况下叶子节点(包括无用的叶子节点)数量为 个,又由于其为一棵完全二叉树,则其总节点个数 。当然如果你懒得计算的话可以直接把数组长度设为 4n,因为 的最大值在 时取到,此时节点数为4n 。
更新
更新节点主要是递归+二分(根据下标)查找指定的节点,将其替换为最新值,递归更新其父节点即可
1 |
|
求和
根据指定的下标left、right求该区间arr[left…right]的数据和。
1 |
|
特性
线段树的惰性传播 (Lazy Propagation in Segment Tree)
惰性传播 (Lazy Propagation in Segment Tree)很多地方又叫做【懒标记】
当我们对某个区间 [left,right] 值进行修改时需要遍历 [left,right]所有节点进行更新。在这种情况引入【懒标记】可以有效降低复杂度。
懒标记:每次执行修改操作时,通过打标记的方式表明该节点对应的区间在某一次操作中被更改,但不更新该节点的子节点信息。正在的修改在下一次访问带有标记的节点时才进行。
对 [2,4] 区间的每个节点进行 +5 的修改操作,可知 [2,4] 包含的节点共有 [2…2], [3…3], [4…4] 共三个节点,其中 [3…3], [4…4] 属于 t[2] 的子节点所以我们可以直接在 t[2] 打上一个懒标记 tag[2] = 5,不对其子节点进行改动,但不影响查询结果。
那什么时候进行更新其子节点呢?当我们进行查询 [3,4] 区间范围和时,发现 [3,4] 区间还存在有懒标记,此时对懒标记进行下放,更新节点值。
1 |
|
线段树的区间求和 (Sum of given range)
在给定的区间 [left,right] 范围内如果存在节点的范围恰好是 [left,right] 那直接返回当前节点值即可,否则需要将 [left,right] 切割成两部分递归查找所属范围在求和即可。
如果要查询的区间为 [2,4],此时就不能直接获取区间的值[2,4],但是 可以拆成 [2,2] 和 [3,4],可以通过合并这两个区间的答案来求得这个区间的答案。
带节点更新的范围最大查询 (Range Maximum Query with Node Update)
查询最大值主要是直接获取存储的最大值,当更新时递归的获取当前范围的节点的最大值进行比较进行更新
1 |
|
进阶
线段树的高效实现 (Segment Tree Efficient Implementation)
线段树的高效实现
1 |
|
采用位运算加速
1 |
|
资料
https://oi-wiki.org/ds/seg/
https://cp-algorithms.com/data_structures/segment_tree.html
可视化 :https://visualgo.net/zh/segmenttree?slide=1
给定范围之和:https://www.geeksforgeeks.org/segment-tree-set-1-sum-of-given-range/?ref=gcse
在线段树中懒标记:https://www.geeksforgeeks.org/lazy-propagation-in-segment-tree/?ref=gcse
线段树的高效实现:https://www.geeksforgeeks.org/segment-tree-efficient-implementation/?ref=lbp