对树状数组的(简单)理解

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

    当时学数据结构的时候,暑假训练的课上得云里雾里,自己还忙于补之前的题,导致只听完了课,内容都没有消化. 学完了树状数组,只知道怎么用,该用的时候还想不起来用...现在细想一下, 简单的树状数组就是 点修改、区间查询,能降一位O(n)到log(n).

    现在有一个序列,a[n],假设n=16. 设

c[1]=a[1]    c[2]=a[1]+a[2]

c[3]=a[3]    c[4]=a[1]+a[2]+a[3]+a[4]

c[5]=a[5]    c[6]=a[5]+a[6]

c[7]=a[7]   c[8]=a[1]+.....+a[8]

........

1.lowbit()

通过观察,我们发现每一个c[i]的节点表示的和中最后一项都是c[i],他们所包含的项的个数是i的二进制最后一位1所代表的数(或者是说2^k,k为末尾零的个数),这也是lowbit() 函数要做的事情

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

其实想要求这个lowbit的话,我们最先想到的是,对于一个数x的二进制,如果我们减去1,那么从末尾开始,所有末尾的0都会变成1,最后一个1也会变成0,只有这部分会变化;所以我们用 x去异或(x-1),就会把我们用得到的位保留下来并置为1,之前没有关系的位都置为0,这一串1再和x进行与运算,就把原来是0的位去掉了,原来是1的位还是1,就得到了我们要求的lowbit的值。

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

i&-i是利用了负数补码的性质,取反加1,进位只影响到最后一位1,非常巧妙,以前一直在照写,却没有明白为什么,实在是惭愧。

那么,所有的c[i]节点构成了一棵树,这个数就是树状数组。

2.get()

int get(int i) {
      int ans=0;
      while (i>0) {
            ans+=c[i];
            i=i-lb(i);
      }
      return ans;
}

查询操作是查询区间和,将节点加到一块。比如我查询的是sum(5),5的二进制是101,从最后一个1开始取节点c[5];减去lowbit变成100,取c[4];减去lowbit变成0,停止;加和即为a[1]+...+a[5],即sum(5).

为什么是O(log(n))呢,因为查询时是二进制数中每有一个1就进行一次操作,做多有log(n)个1。

3.set()

void set(int i, int x) {
      while (i<=N) {
            s[i]+=x;
            i=i+lb(i);
      }
}

修改包含当前要修改的位置的节点即可。i每次加lowbit是将包含这个节点的值都进行一次修改,包含一个a[i]节点的c[i]节点最多有log(n)+1个。拿16和1举例,显而易见,1,2,4,8,16都包含1。复杂度还是O(log(n))级别

树状数组还能转变为区间修改点查询。

4.个人理解

    觉得树状数组之所以能降低到O(log(n))的复杂度是因为,树状数组用类似二分的办法将数组储存下来。16分为左8整体,左8又被拆出来左4,以此类推......  还有就是,利用二进制,或者说是2的次方的性质。 log也是以2为底的~  降复杂度有时候就跟二分或者二进制(的胡搞...)有关。稍微高级一点的应用过几天在学2333

猜你喜欢

转载自blog.csdn.net/tju_peter/article/details/79504231
今日推荐