树状数组(初识)

参考:树状数组

简述:一个用来求数组列前缀和的数据结构,可以在O( log(n) )的时间内得到数列的任意前缀和,同样以相同时间对某项加一个常数,是一个很强大的数据结构。

认识:有段时间一直以为树状数组就是普通的数组,只不过可以用它来存储二叉查找树,后来才知道这货其实是一个很难的东西。不过说难,其实它也是一个蛮简单、蛮好入手的一个东西——关键代码相当简洁,只是有点难以理解,但是,用一个图就可以表达清楚了:
技术分享
PS:这里盗图一用(来源

如果还没有清楚,很正常,来看看一个具体的例子:
有数组a,a = {1, 2, 3, 4, 5, 6, 7, 8},然后有一个bit数组,储存树状数组用的,它的值如下:
bit[1] = a[1] ;
bit[2] = a[1]+a[2] ;
bit[3] = a[3];
bit[4] = a[1]+a[2]+a[3]+a[4] ;
bit[5] = a[5] ;
bit[6] = a[5]+a[6];
bit[7] = a[7] ;
bit[8] = a[1]+a[2]+a[3]+a[4]+a[5]+a[6]+a[7]+a[8] .
然后可以发现,上面这个数组的各个值,又有一个规律:
bit[1] = a[1] ;
bit[2] = a[2]+bit[1] ;
bit[3] = a[3];
bit[4] = a[4]+bit[2]+bit[3] ;
bit[5] = a[5] ;
bit[6] = a[6]+bit[5] ;
bit[7] = a[7] ;
bit[8] = a[8]+bit[4]+bit[6]+bit[7] .
把上面的八个式子跟那个图对应着看,有没有发现什么神奇的东西?
嗯,其实那个图,想表达的就是那几个式子,从那几个例子我们其实可以隐隐约约看出bit数组的规律,但是这东西怎么用程序来表达呢?

1、首先得有一个预备函数lowbit( int ),返回参数转为二进制后最后一个1的位置代表的数值:

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

这里用了一些位运算操作。

2、然后就是bit数组的构建了,等于啥呢?

void build()
{
    for(int i=1;i<=n;i++)
    {
        bit[i]=a[i];
        for(int j=i-1;j>i-lowbit(i);j-=lowbit(j))
            bit[i]+=bit[j];
    }
}

这段代码表达的就是那个图,至于为啥,读者自己思考吧,会有很大收获哦!

3、接下来看看这东西怎么做到高速求前缀和的:

int sum(int index)
{
    int res=0;
    for(int i=index;i>0;i-=lowbit(i))
        res+=bit[i];
    return res;
}

如果你理解了上面的build函数,那这段代码必然是秒懂,这里我就举个例子吧:
还是上面那个8位数组,用sum数组表示a数组的前缀和:
sum[1] = bit[1] ;
sum[2] = bit[2] ;
sum[3] = bit[2]+bit[3] ;
sum[4] = bit[4] ;
sum[5] = bit[4]+bit[5] ;
sum[6] = bit[4]+bit[6] ;
sum[7] = bit[4]+bit[6]+bit[7] ;
sum[8] = bit[8] .
参照代码想一下下……

4、修改怎么破,如果给a[i]增加Δ:
直接上代码吧:

void add(int index,int delta){
    for(int i=index;i<=n;i+=lowbit(i))
        bit[i]+=delta;
}

看懂了2,就看懂了3、4!

总结:
1、优美的数据结构,不能再美!
2、用二进制思考问题!

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。