树状数组详解

树状数组是干什么的?

一句话概括:树状数组是查询动态数组前缀和的。一般而言,对于一个值经常变化的数组,我们修改某一个值的复杂度是O(1),查询某一个前缀和是O(n),而使用树状数组,则可以做到查询和修改的复杂度均为O(logn),降了一个数量级。

树状数组的结构和原理

引用百度百科的图片:
在这里插入图片描述
上图为一个树状数组的结构:
下面我用 A [ n ] A[n] A[n]表示原数组的元素, T [ n ] T[n] T[n]表示树状数组的元素。可见每一个树状数组的元素代表的一段原数组的元素的和,例如
T [ 12 ] = A [ 12 ] + A [ 11 ] + A [ 10 ] + A [ 9 ] T[12] = A[12] + A[11] + A[10] + A[9] T[12]=A[12]+A[11]+A[10]+A[9]
T [ 11 ] = A [ 11 ] T[11] = A[11] T[11]=A[11]
T [ 10 ] = A [ 10 ] + A [ 9 ] T[10] = A[10] + A[9] T[10]=A[10]+A[9]
T [ 9 ] = A [ 9 ] T[9] = A[9] T[9]=A[9]
T [ 8 ] = A [ 8 ] + A [ 7 ] + A [ 6 ] + A [ 5 ] + A [ 4 ] + A [ 3 ] + A [ 2 ] + A [ 1 ] T[8] = A[8] + A[7] + A[6] + A[5] + A[4] + A[3] + A[2] + A[1] T[8]=A[8]+A[7]+A[6]+A[5]+A[4]+A[3]+A[2]+A[1]
T [ 7 ] = A [ 7 ] T[7] = A[7] T[7]=A[7]
T [ 6 ] = A [ 6 ] + A [ 5 ] T[6] = A[6] + A[5] T[6]=A[6]+A[5]
T [ 5 ] = A [ 5 ] T[5] = A[5] T[5]=A[5]
T [ 4 ] = A [ 4 ] + A [ 3 ] + A [ 2 ] + A [ 1 ] T[4] = A[4] + A[3] + A[2] + A[1] T[4]=A[4]+A[3]+A[2]+A[1]
T [ 3 ] = A [ 3 ] T[3] = A[3] T[3]=A[3]
T [ 2 ] = A [ 2 ] + A [ 1 ] T[2] = A[2] + A[1] T[2]=A[2]+A[1]
T [ 1 ] = A [ 1 ] T[1] = A[1] T[1]=A[1]
可以发现每个树状数组的元素保存的是一段数据的和,如果我们想要得到12的前缀和,那么可以通过
T [ 12 ] + T [ 8 ] T[12] + T[8] T[12]+T[8]得到,可以再 O ( l g N ) O(lgN) O(lgN)的得到,同样如果要更改 A [ 6 ] A[6] A[6]的值,我们要把 T [ 6 ] , T [ 8 ] , T [ 16 ] T[6],T[8],T[16] T[6],T[8],T[16]的要同时修改掉,更改的时间复杂度是 O ( l g N ) O(lgN) O(lgN),这样我们就可以维护动态的前缀和了。
这里有一个挺难发现的规律,对于树状数组的每一项,他到底是管辖多少个元素呢?比如 T [ 12 ] T[12] T[12]管辖四个元素。其实 T [ i ] T[i] T[i]管辖元素的个数是 2 k , k 2^k,k 2kk的值是 i 的二进制末尾连续0的个数,大家可以自行验证一下。

Lowbit运算

上回合说到,我们要想得到树状数组的元素,比如 T [ i ] T[i] T[i],必须要知道i的二进制末尾零的个数 k k k,然后 2 k 2^k 2k,为 T [ i ] T[i] T[i]管辖的范围。前辈给我们提供了非常简洁的做法快速计算 i i i 对应的 2 k 2^k 2k,这个运算有专门的名字叫Lowbit运算。

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

函数的参数是 T [ i ] T[i] T[i]中的 i i i ,返回值是 2 k 2^k 2k,为什么会这样,大家首先要了解计算机是怎么存储负数的,然后就明白了。

求前缀和

有上面的介绍,我们就可以通过Lowbit运算轻易求出元素的前缀和。

int getSum(int i){
    int res = 0;
    while(i > 0){
        res += T[i];
        i -= lowbit(i);
    }
    return res;
}

更新树状数组元素

同样对于更新数组元素 A [ i ] A[i] A[i]的过程,我们只需要将包含 A [ i ] A[i] A[i]的所有 T [ ] T[] T[],都进行更改就可以了。

// 对指定A[i]加上value操作
void update(int i, int value){
    A[i] += value;    //不能忘了对A数组进行维护,尽善尽美嘛
    while(i <= n){
        T[i] += value;
        i += lowbit(i);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值