文章目录
1 线段树(又名为线段修改树)
线段树所要解决的问题是,区间的修改,查询和更新,如何更新查询的更快?
线段树结构提供三个主要的方法, 假设大小为N的数组,以下三个方法,均要达到O(logN) :
// L到R范围的数,每个数加上V
void add(int L, int R, int V, int[] arr);
// L到R范围的数,每个数都更新成V
void update(int L, int R, int V, int[] arr);
// L到R范围的数,累加和返回
int getSum(int L, int R, int[] arr);
1.1 线段树概念建立
1.1.1 累加和数组建立
1、对于大小为n的数组,我们二分它,每次二分我们都记录一个信息
2、对于每次二分,成立树结构,我们想拿任何区间的信息,可以由我们的二分结构组合得到。例如我们1到8的数组,可以二分得到的信息为:
graph TD
'1-8'-->'1-4'
'1-8'-->'5-8'
'1-4'-->'1-2'
'1-4'-->'3-4'
'5-8'-->'5-6'
'5-8'-->'7-8'
'1-2'-->'1'
'1-2'-->'2'
'3-4'-->'3'
'3-4'-->'4'
'5-6'-->'5'
'5-6'-->'6'
'7-8'-->'7'
'7-8'-->'8'
每一个节点的信息,可以由该节点左右孩子信息得到,最下层信息就是自己的信息。由以上的规则,对于N个数,我们需要申请2N-1个空间用来保存节点信息。如果N并非等于2的某次方,我们把N补成2的某次方的长度,用来保证我们构建出来的信息数是满二叉树。例如我们的长度是6,我们补到8个,后两个位置值为0。
对于任意的N,我们需要准备多少空间,可以把N补成2的某次方,得到的二分信息都装下?答案是4N。4N虽然有可能多分空间,但是多余的空间都是0,并无影响,而且兼容N为任意值的情况
例如四个数长度的数组arr[4]{3,2,5,7},我们得到累加和的二分信息为如下的树:
graph TD
'1到4=17'-->'1到2=5'
'1到4=17'-->'3到4=12'
'1到2=5'-->'3'
'1到2=5'-->'2'
'3到4=12'-->'5'
'3到4=12'-->'7'
我们申请4N的空间,即16,arr[16]。0位置不用。arr[1]=17,arr[2]=5,arr[3]=12,arr[4]=3,arr[5]=2,arr[6]=5,arr[7]=7。剩下位置都为0。任何一个节点左孩子下标为2i,右孩子下标为2i+1
得到累加和信息的分布树的大小,和值的情况,那么update更新树,和add累加树,同样的大小和同样的坐标关系构建。
1.1.2更新结构数组建立
懒更新概念,例如有8个数,我们要把1到6的数都减小2。那么先看1到6是否完全囊括8个数,如果囊括直接更新。很显然这里没有囊括,记录要更新1到6,下发该任务给1到4和5到8。1到6完全囊括1到4,记录到lazy中,不再下发;5到8没有囊括1到6,继续下发给5到6和7到8,5到6被囊括,记录到lazy不再继续下发,7到8不接受该任务
这种懒更新机制的时间复杂度为O(logN),由于一个区间经过左右子树下发,只会经过一个绝对路径到叶子节点,其他节点都会被懒住。如果某个节点有新的任务进来,会把之前懒住的信息下发给左右孩子
对于update操作,如果update操作经过的信息节点上存在懒任务,那么该次update操作会取消该节点的lazy,无需下发,因为下发了也会给update覆盖掉;
public class Code01_SegmentTree {
public static class SegmentTree {
// arr[]为原序列的信息从0开始,但在arr里是从1开始的
// sum[]模拟线段树维护区间和
// lazy[]为累加懒惰标记
// change[]为更新的值
// update[]为更新慵懒标记
private int MAXN;
private int[] arr;
// 4*arr.length()
private int[] sum;
// 4*arr.length()
private int[] lazy;
// 4*arr.length()
private int[] change;
// 4*arr.length()
private boolean[] update;
// 根据int[] origin来初始化我们的线段树结构
public SegmentTree(int[] origin) {
MAXN = origin.length + 1;