线段树(通过数组的构建方式)

这里只说最基础的线段树,不涉及lazy标记和延迟修改的操作

用处


  • 面向数组中的数据,针对其中编号是连续的区间进行修改统计的操作

举一个小栗子,假设我们现在有10000个整数,存在 A [ 1 ] − A [ 10000 ] A[1]-A[10000] A[1]A[10000]
统计操作: 统计 [ L , R ] [L, R] [L,R] 的数字之和
修改操作: 将第 L L L 个数增加 C C C

如果不使用线段树,基本的处理方法有这两种:
法1: 用原始数组进行存储,进行统计操作时候将 R-L+1个数进行相加,进行修改操作时直接修改对应位置的数字
法2: 新建一个求和数组 S [ n ] S[n] S[n],其中 S [ 0 ] = 0 , S [ i ] = S [ i − 1 ] + A [ i ] S[0] = 0, S[i] = S[i-1] + A[i] S[0]=0,S[i]=S[i1]+A[i],进行统计操作的时候计算 S [ R ] − S [ L − 1 ] S[R] - S[L - 1] S[R]S[L1],进行修改操作的时候同时修改 S [ L , 10000 ] S[L, 10000] S[L,10000]

那么这两个方法的时间复杂度分别是

-法1法1时间复杂度法2法2时间复杂度
统计计算 R − L + 1 R-L+1 RL+1 O ( n ) O(n) O(n)计算1次 O ( 1 ) O(1) O(1)
修改修改一个元素 O ( 1 ) O(1) O(1)修改 10000 − L + 1 10000-L+1 10000L+1个元素 O ( n ) O(n) O(n)

不难发现,统计和修改不能两全,这时候就用到了线段树。



原理


假设需要处理的数组含有 n n n 个元素,为 A [ 1 ] − A [ n ] A[1]-A[n] A[1]A[n]
需要将 [ 1 , n ] [1, n] [1,n] 分割成若干特定的子区间,通过对少量特定子区间进行修改,来实现对 [ L , R ] [L, R] [L,R] 的快速修改和统计

用线段树进行统计的东西,必须符合区间加法。符合区间加法的例子有:

  • 数字之和:sum = sum(左区间) + sum(右区间)
  • 最大值:max = max(左区间) + max(右区间)

不符合区间加法的例子:

  • 众数:more ≠ more(左区间) + more(右区间)

下面就逐一讲线段树的各个步骤

子区间的划分

对于给定区间 [ L , R ] [L, R] [L,R],只要 L ≠ R L ≠ R L=R,就将其分为两个区间,分割方式和二分法类似,求取中间值 m i d = ( L + R ) / 2 mid = (L + R) / 2 mid=(L+R)/2,再求 左 区 间 = [ L , m i d ] 左区间=[L, mid] =[L,mid] 右 区 间 = [ m i d + 1 , R ] 右区间=[mid + 1, R] =[mid+1,R],直到 L = = R L==R L==R为叶子节点
下图中的 [ ] [] [] [ L , R ] [L, R] [L,R],存储的内容为执行区间加法后的结果
在这里插入图片描述
如此这般划分之后,执行统计操作只要找到对应区间(如 [ 2 , 12 ] = [ 2 ] + [ 3 , 4 ] + [ 5 , 7 ] + [ 8 , 10 ] + [ 11 , 12 ] [2, 12] = [2] + [3, 4] + [5, 7] + [8, 10] + [11, 12] [2,12]=[2]+[3,4]+[5,7]+[8,10]+[11,12]),执行修改操作也是找到对应区间 (如修改 [ 2 ] [2] [2],即需要修改 [ 2 , 2 ] → [ 1 , 2 ] → [ 1 , 4 ] → [ 1 , 7 ] → [ 1 , 13 ] [2, 2] → [1, 2] → [1, 4] → [1, 7] → [1, 13] [2,2][1,2][1,4][1,7][1,13])
那么无论是统计还是修改,时间复杂度都为 O ( log ⁡ n ) O(\log n) O(logn)

存储结构

  • 用二叉树进行存储,每个节点包含left, right, value, left_child, right_child(左右区间,左右孩子,区间加法值)
  • 用数组进行存储,建立时用 4 n 4n 4n 的空间大小,父节点 v v v 的两个孩子分别为 2 v + 1 2v+1 2v+1 2 v + 2 2v+2 2v+2 r o o t = 0 root=0 root=0

这里给出使用数组的构建线段树方法

public int buildTree(int current, int left, int right){
    if(left == right){
        biTree[current] = nums[left];
        return nums[left];
    }
    
    int mid = (left + right) / 2;
    int leftNum = buildTree(2 * current + 1, left, mid);
    int rightNum = buildTree(2 * current + 2, mid + 1, right);
    biTree[current] = leftNum + rightNum;
    return biTree[current];
}

查询方法

public int findRange(int currentNode, int left, int right, int currentLeft, int currentRight){
    if(currentLeft == left && currentRight == right)
        return biTree[currentNode];
    
    int mid = (currentLeft + currentRight) / 2;
    
    if(left > mid)
        return findRange(2 * currentNode + 2, left, right, mid + 1, currentRight);
        
    if(right <= mid)
        return findRange(2 * currentNode  + 1, left, right, currentLeft, mid);
    
    int leftNum = findRange(2 * currentNode  + 1, left, mid, currentLeft, mid);
    int rightNum = findRange(2 * currentNode + 2, mid + 1, right, mid + 1, currentRight);
    
    return leftNum + rightNum;
}

修改方法

    public void change(int currentNode, int val, int index, int currentLeft, int currentRight){
    if(currentLeft <= index && index <= currentRight)
        biTree[currentNode] += val;
    if(currentLeft == currentRight || index > currentRight || index < currentLeft)
        return;
    
    int mid = (currentLeft + currentRight) / 2;
    change(currentNode * 2 + 1, val, index, currentLeft, mid);
    change(currentNode * 2 + 2, val, index, mid + 1, currentRight);
    return;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值