树状数组学习(leetcode 307)

树状数组

学习自https://www.cnblogs.com/findview/archive/2019/08/01/11281628.html

文中提到的A代表原数组,C代表树状数组,下文不在说明。

树状数组解决的问题

解决大部分基于区间上的更新以及求和问题。例如输入一个长度为n的数组,然后求某个区间的和或者对指定下标位置修改。

时间复杂度:
1. 建树 O(n)
2. 更新 O(logN)
3. 查询prefixSum(i) O(logN)(1-i求和)
4. 查询rangeSum(i,j) O(logN)(区间求和)

树状数组基本原理

先来看一个图,树状数组就是红色的(用C表示),原数组是黑色的(用A表示)

有这样一个规律

C1 = A1
C2 = C1 + A2 = A1 + A2
C3 = A3
C4 = C2 + C3 + A4 = A1 + A2 + A3 + A4
C5 = A5
C6 = C5 + A6 = A5 + A6
C7 = A7

我们称C[i]的值为下标为i的数所管辖的数的和,C[8]存放的就是被索引8所管辖的那些数的和(有8个),而下标为i的数所管辖的元素的个数则为2^k个(k为i的二进制的末尾0的个数)8的二进制是1000,所以是2^3=8

所以该如何求末尾的连续0的个数呢?我们有一个lowbit方法,这个方法算出来结果的并不是k,而直接是2^k,

int lowbit(int m){return m&(-m);}

建树状数组

有了以上的基础我们就可以建树状数组了,其实就是利用lowbit算出每一个C[i]管辖的个数,然后累加。

    NumArray(vector<int>& nums) {
        A = nums;
        size = nums.size();
        int c,count,j;
        for(int i=1;i<=size;i++){
            count = lowbit(i);//管辖的个数
            c = 0;j = i;
            while(count--){
                c += A[j-1];
                j--;
            }
            C.push_back(c);
        }
    }

利用树状数组求前缀和

这里举两个例子sum(7),sum(6)

sum(7) = C7 + C6 + C4

sum(6) = C6 + C4

那么如何知道sum(7)就是由树状数组中第7 6 4这三个元素的和呢?还是lowbit方法,设求sum(m),从C[m]开始累加,每一次索引都减去2^k(m&-m).7的二进制为0111(C7得到),那么先对0111的末尾1的位置-1,得到0110 == 6(C6得到),再对0110末尾1位置-1,得到0100 == 4(C4得到),最后对0100末尾1位置-1后得到0000(结束信号),计算停止,至此C7,C6,C4全部得到,求和后就是m == 7时它的前缀和。

对于查询的m,将它转换成二进制后,不断对末尾的1的位置进行-1的操作,直到全部为0停止

int prefixSum(int x){
    int sum = 0;
    while(x){
        sum += C[x-1];//这里是因为我们之前的索引都是从1开始,但是数组中是从0开始
        x -= lowbit(x);
    }
    return sum;
}

利用树状数组区间求和

区间求和的思路就是前缀和相减。例如求sum[i,j]闭区间,就等于prefixSum(j)-prefixSum(i-1) 就可以得到答案。

单点更新树状数组

对于输入索引为x的值,要求为它的值修改为val值。修改其实就是求前缀和倒过来。

假设修改的x=2,我们把增加的值设为delta。如果修改了A[2]的值,那么与A[2]的C[2],C[4],C[8](所有的祖先节点)的前缀和都要加上delta。

我们如何得到C2的所有祖先节点呢(因为C2和A2的下标相同所以更新时查询从C[x]开始)

对于要更新x位置的值,我们把x转换成二进制,不断对二进制最后一个1的位置+1,直到达到原数组的size结束

假设x=2,size=8,x转换成二进制后等于0010(C2),对末尾1的位置进行+1,得到0100(C4),对末尾的1的位置进行+1,得到1000(C8),结束,对C2,C4,C8的前缀和都要加上delta,当然不能忘记对A[2]的值+delta,单点更新值过程结束。

void update(int i, int val) {
    int delta = val - A[i];
    A[i] = val;//这里别忘了修改原数组
    i++;
    while(i<=size){
        C[i-1] += delta;
        i += lowbit(i);
    }
}

好了以上就是学习的内容,虽然现在还不是特别理解,但是先码下来。

如果大家想练习一下,见leetcode 裸树状数组 https://leetcode-cn.com/problems/range-sum-query-mutable/

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值