树状数组整理

来源:互联网 发布:防火知多少教案 编辑:程序博客网 时间:2024/06/13 07:34

树状数组是一个简单而且好用的数据结构,只有更新和查询操作,都可以在log(n)的复杂度内完成操作。
其C++实现代码为:

int lowbit(int x){    return x&(-x);}void add(int i,int x){    for(;i <= n;i+=lowbit(i)){        a[i]+=x;    }}int sum(int i){    int ans = 0;    for(;i>0;i-=lowbit(i)){        ans += a[i];    }    return ans;}

lowbit()函数

lowbit()函数有什么作用呢?返回那个数二进制最后一位1所代表的数字,也就是2^k。比如,lowbit(2)是2,2的二进制是10,lowbit(15)是1,15的二进制是1111。lowbit()函数使得数组的下标具有了父子关系,可以通过加减lowbit()的返回值来访问当前下标的父亲或儿子,使连续的数组具有类似树的结构。
要想明白lowbit()函数的实现与计算机以补码储存数有关.正数的补码就是他本身,负数的补码要将其二进制按位取反然后加1。举个例子,+5的补码为0101,-5的补码为1011;+6的补码为0110,-6的补码为1010。很容易发现,如果将正数与其对应的负数进行与操作,返回的正好是其二进制最后一位1所代表的值,5&(-5)=1,6&(-6)=2,lowbit()函数就是用这种方法实现的。

树状数组实现

数组是具有连续下标的序列,树是由点组成的具有层次关系的集合,树状数组是这两种关系的集合,对于两个下标x,y,如果x+2^k=y,则称y是x的父节点,x是y的子节点,lowbit()函数就是用来快速求2^k的。
树状数组维护的是区间和,区间的右端点显而易见就是当前坐标i,左端点则是通过树找到的最左儿子,那么求和时只需将小于i的所有区间加到一起就是1~i的区间和。
下面我们将利用树状数组解决一个简单的问题来理解树状数组:
现在有9堆石子,编号分别为1~9,每堆有对应其编号的石子数,现在问第5~9有多少堆石子。
这个问题很简单,如果我们用树状数组来解决这个问题,则刚好涉及到了树状数组的两个操作,add()函数和sum()函数。问题的答案为sum(9)-sum(4)。
我们维护一个数组a
这里写图片描述

  • add()操作
    将第一堆插入到数组中
    a[1]+=1;lowbit(1)=1,a[2]+=1;lowbit(2)=2,a[4]+=1;lowbit(4)=4,a[8]+=1;
    这里写图片描述
    将第二堆插入到数组中
    a[2]+=2;a[4]+=2;a[8]+=2;
    这里写图片描述
    将第三堆插入到数组中
    a[3]+=3;lowbit(3)=1,a[4]+=3;a[8]+=3;
    这里写图片描述
    将第四堆插入到数组中
    a[4]+=4;a[8]+=4;
    这里写图片描述
    将第五堆插入到数组中
    a[5]+=5;lowbit(5)=1,a[6]+=5;lowbit(6)=2,a[8]+=5;
    这里写图片描述
    将第六堆插入到数组中
    a[6]+=6;a[8]+=6;
    这里写图片描述
    将第七堆插入到数组中
    a[7]+=7;lowbit(7)=1,a[8]+=7;
    这里写图片描述
    第八堆,第九堆插入到数组中
    a[8]+=8;a[9]+=9;
    这里写图片描述

  • sum()操作
    lowbit(9)=1,9-lowbit(9)=8,8-lowbit(8)=0;
    sum[9] = a[9]+a[8] = 9+36 = 45;
    lowbit(4)=4,4-lowbit(4) = 0;
    sum(4) = a[4] = 10;
    答案为35。

总结

我所理解的树状数组大致是这样了,树状数组还有很多巧妙的用法,要学习的还有很多。在应用中要注意树状数组是从下标1开始的,要避免0的情况出现,会死循环,我的解决方法是预处理下数据,比如全都加个1,可能还有更好的办法吧。如果觉得我讲的不够详细可以看看这篇博客,博客后面还有练习题。

原创粉丝点击