线段树模板的理解和使用
线段树简述
线段树是用来维护 区间信息 的数据结构。线段树的每个节点代表一个区间。
线段树可以在 的时间复杂度内实现单点修改、区间修改、区间查询(区间求和,求区间最大值,求区间最小值)等操作。
线段树的模板
线段树中,区间的范围给定之后,树的结点就固定了。
使用链表表示线段树
预先定义:
1 | class Node{ |
线段树的构建模板写法:
1 | public void buildTree(Node node, int start, int end) { |
线段树动态开点模板写法:
1 | // 动态开点 |
从上面的代码可以看出,不管是 update,还是 query,在进行下层递归时都是使用 [start, mid] 和 [mid + 1, end]。它表示数的当前结点表示的区间范围。
递归时,不管是 update,还是 query,[l,r] 表示查询区间,参数传递时是不变的。不要自行分割区间,如:
1 | // 错误写法 |
上面的做法是错误的,因为你可能会把查询的区间扩大了,也就是说,上面的代码没有考虑到 r<mid 或 l>mid+1 的情况。
递归时的终止条件中,我们并没有检查 start <= end,即不考虑 start > end 的情况,那么 update,还是 query 在递归过程中传入的参数会出现 start > end 吗?答案是不会的。推导 [1] 如下:
- 因为本层传入
start > end只有在上一层有start==end并计算mid=(start+end)/2;时,对右子区间递归时才可能出现。 - 然而如果上一层传入的参数
start==end,那么必有l <= start && end <= r,它只会在第一个条件判断语句(边界条件)中跳出递归,不会进行后续的mid计算,更无下一层的递归。 - 因此不会出现传过来的参数
start > end。
对于终止条件的风格,还有以下这种,两者是等价的:
1 | public void update(Node node, int start,int end,int l,int r,int val){ |
在不同的题目中,val 和 add 可以扮演不同的角色,比如区间和或者区间最值。这时候就需要根据题目要求修改 val 和 add 的计算代码。
使用数组表示线段树

我们知道,(二叉)堆的逻辑结构是树形的,但其存储结构是数组。树根编号为 1。假设某结点的编号为 p,则其左孩子的编号为 2p,右孩子编号为 2p+1。

代码写法:父结点编号 p,左子节点编号 p<<1,右子节点编号 p<<1|1。注意,不建议使用移位运算配合加号使用,因为加号优先级更高,因此需要引入额外的括号。
优先级的记忆口诀:单算移关与,异或逻条赋
括号级别最高,逗号级别最低,单目 > 算术 > 位移 > 关系 > 逻辑 > 三目 > 赋值。
就是把原先在链式结构中 Node 的属性值变为数组即可。注意取的数组大小至少为 4N,如果题目中所需的 N 过大可能会导致内存超限。
关于使用堆式存储时数组大小设置成 4N 的原因
4N 的原因关于线段树的空间:如果采用堆式存储,若有 n 个叶子结点,则 d 数组的范围最大为 。
分析:容易知道线段树的深度是 的,则在堆式储存情况下叶子节点(包括无用的叶子节点)数量为 个,又由于其为一棵完全二叉树,则其总节点个数 。当然如果你懒得计算的话可以直接把数组长度设为 4n,因为 的最大值在 时取到,此时节点数为 。
而堆式存储存在无用的叶子节点,可以考虑使用内存池管理线段树节点,每当需要新建节点时从池中获取。自底向上考虑,必有每两个底层节点合并为一个上层节点,因此可以类似哈夫曼树地证明,如果有 n 个叶子节点,这样的线段树总共有 2n-1 个节点。其空间效率优于堆式存储,并且是可能的最优情况。
1 | int N; |
什么情况下用动态开点线段树?
如果只是需要一次构建并求出结果,就使用一次归并的思路解决(写 build 函数即可)。
如果是多次更新与查询混合交替(在 LeetCode 中这种题目一般是叫你补充完整一个类),那就使用动态开点和懒惰标记模板,写一个完整的线段树。
根据题目实际情况,两个模板按需默写。比如在这些题目中:
- 只需要默写动态开点模板
- 需要从头到尾构建一棵完整的线段树
- 🟨 53. 最大子数组和 - 力扣(LeetCode)。题解可参考 站内文章本文 中线段树的解法。
- 需要从头到尾构建一棵完整的线段树,再动态更新
本文参考
- ⭐主要参考/推荐阅读:线段树详解 - Lfool
- 我的 408 笔记
- 线段树 - OI Wiki
- Java运算符及运算符的优先级_java运算符优先级-CSDN博客
尝试将上面的话抽象为命题并用形式语言证明?一点点参考:站内文章你真的弄懂假言判断(条件命题)了吗?。 ↩︎





