关于树状数组

前言

  前端时间在网上看了很多树状数组的讲稿,但是很多东西都不能一下子理解。试着把关于这个数据结构的东西梳理成博客来加深理解和记忆。

基本概念

  树状数组就是把原本的序列换一种方式存储,从而在保留原始值(可查询)的同时,优化更改和询问的时间复杂度。

  至于如何构造,先放张(被大家广泛使用的)图好了!

 

 


其中,C为我们所使用的树状数组,A为输入的数组,有:

C[1]=A[1], C[3]=A[3], C[5]=A[5], C[7]=A[7];

C[2]=C[1]+A[2],C[6]=C[5]+A[6];

C[4]=C[2]+C[3]+A[4] (=A[1]+A[2]+A[3]+A[4]);

C[8]=C[4]+C[6]+C[7]+A[8] (=A[1]+A[2]+A[3]+A[4]+A[5]+A[6]+A[7]+A[8]).

这样一看是有明显的规律的,但是不好表述,那么我们根据其原理,将下标转化为2进制,就会好理解很多。

十进制 1 2 3 4 5 6 7 8
二进制 1 10 11 100 101 110  111 1000

()2下末尾1

1 2 1 4 1 2 1 8
原x个数的和 1 2 1 4 1 2 1 8

 

 

 

 

因此,树状数组本身已经给我们提供了一种O(log n)的求前缀和的方法。

即,sum(A[1]..A[x])=将x转化为2进制后,各位1表示大小的C[ ]值相加。

那么如何实现这点,这里引入了lowbit函数,即返回一个数二进制最后的“1”所表示的数大小。

常用的两种lowbit函数如下:

int lowbit(int x){
    return(x&(-x));
}
lowbit_1
int lowbit(int x){
        return(x-x&(x-1));
}
lowbit_2
void query(int x){
    int sum=0;
    for(;x>0;x-=lowbit(x))sum+=c[lowbit(x)];
}
query

而我们需要用到树状数组,主要还是为了实时修改更新的复杂度(以及较低的代码复杂度)。因此当我们要对某个结点进行单点修改时,还需要修改在前缀和中包含此节点的一些结点的值。

也即,以修改a[5]为例,我们需要维护c[5],c[6],c[8]。

转化为二进制可能可以看得更加清晰:

c[1001]--->c[1010]---->c[10000]。

对于每一位1,我们先维护它本身,然后x+=lowbit(x);(满足x<=n)

void modify(int x,int delta){
    while(x<=n){
        c[x]+=delta;
        x+=lowbit(x);
    }
}
单点修改

区间查询则可以直接用前缀减代换,即sum[l][r]=sum[r]-sum[l-1]

 

TBC.

posted @ 2018-10-23 20:28  Zhangyihhh  阅读(86)  评论(0)    收藏  举报