数据结构之树状数组(BIT)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Q1410136042/article/details/82459725

Binary index tree,二叉索引树,又叫树状数组,用于动态连续查询为你。给定一个数组A1、A2、A3......An,树状数组支持以下两种操作:

  • add(x,d):让A[x]增加d,时间复杂度O(log n)
  • sum(x):求A前x个元素的和,时间复杂度O(log n),有了前缀和就可以O(1)地求任意区间的和了

在说树状数组前,我们先了解一下lowbit(x)对于正整数x,我们定义lowbit(x)为x的二进制表达式中最右边的1所对应的值,比如38288的二进制是1001010110010000,所以lowbit(38288)=10000(2),也就是16。然后一般写程序的时候,lowbit(x)=x&-x——例如x的二进制是 a b c d 1 0 0 0(a,b,c,d是0或1),则-x是x的取反加一,即-x = (!a) (!b) (!c) (!d) 1 0 0 0,则x&-x = 1000(2),这就是lowbit(x)的值

然后我们考虑树状数组C(注意,原A数组并不存,只存C数组),C数组是什么呢?

定义        C[i] = \sum_{j=i-lowbit(i)+1}^{i} A[j]

就像下图(图片来自百度百科)

  • C[1]=A[1]
  • C[2]=C[1]+A[2]=A[1]+A[2]
  • C[3]=A[3]
  • C[4]=C2+C3+A[4]=A[1]+A[2]+A[3]+A[4]
  • C[5]=A[5]
  • C[6]=C[5]+A[6]=A[5]+A[6]
  • C[7]=A[7]
  • 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]

了解了C数组的定义,现在来考虑怎么修改A[i]和查询A的前缀和了。

先说修改,考虑修改A[i]会影响到C数组哪些元素。由上图可以看到修改A[1]首先会影响到C[1],然后C[1]影响C[2],C[2]会影响到C[4],C[4]会影响到C[8],再看修改C[5]会影响到C[6],C[6]会影响到C[8]——这有什么规律呢?额,反正我刚学的时候是看不出来的,所以我就直说了,修改A[i]首先会影响到C[i]的值,然后影响到C[i+lowbit(i)]的值,然后影响到C[(i+lowbit(i))+lobit(i+lowbit(i))],依次类推,直到n(A数组的大小)为止。并且这些元素的改变量和A[i]的改变量一样(这很显然~~)。由于修改需要遍历树高,所以复杂度是O(log n)。这显然比直接修改A[i]复杂度要高,但树状数组的优点是log n的查询,修改是为了查询服务的。

说完修改,再来说求前缀和,比如我们要求A[1]+A[2]+...+A[7],由上图可知,只需要求C[7]+C[6]+C[4]即可,7到6,再到4(也就是图中的蓝色线条),这三个数有什么关系呢?如果修改的原理已经弄懂了,那么这个关系应该不难看出来——7-lowbit(7)==6,6-lowbit(6)==4,也就是求前A的前i项和只需要求C[i] + C[i-lobit(i)] + C[(i-lowbit(i)) - lowbit(i-lowbit(i))] + .....,一直到0为止,这也要遍历树高,所以复杂度也是O(log n)。就像上面说的虽然在修改上面花费了更多的时间,但我们在查询能够更优!!!

然后代码喽:

const int maxn = (int)1e7+100;

inline int lowbit(int x)   {   return x&-x;}

//一维树状数组
int C[maxn];
int sum(int x)
{
    int ret = 0;
    while(x > 0)    ret += C[x], x -= lowbit(x);
    return ret;
}

void add(int x, int d)
{
    while(x <= maxn)
        C[x] += d, x += lowbit(x);
}

//二维树状数组
int C2[maxn][maxn];
int sum(int x, int y)
{
    int ret = 0;
    for(int i = x; i > 0; i -= lowbit(i))
        for(int j = y; j > 0; j -= lowbit(j))
            ret += C2[i][j];
    return ret;
}

void add(int x, int y, int d)
{
    for(int i = x; i <= maxn; i += lowbit(i))
        for(int j = y; j <= maxn; j += lowbit(j))
            C2[i][j] += d;
}

这代码里面给出了二维的树状数组,这就懒地解释了,毕竟了解了一维的原理之后,二维的就很简单了,自己推一下就好~~~

猜你喜欢

转载自blog.csdn.net/Q1410136042/article/details/82459725