树状数组的探索性理解

给定一个数组a[n],求数组a[n]的和sum。通常的方法是遍历数组而后求和,这样的时间复杂度为O(n)。而当修改了数组中的元素,再次求数组的和时,又要付出O(n)的时间代价。此时,咱们能够用树状数组来求和数组的和。获得树状数组C[n]后,时间复杂度将由O(n)变为O(lgn)。这是如何实现的呢?下面咱们将按照:数组

Lowbit()函数-->树状数组-->树状数组索引的意义-->树状数组的求和函数-->被操做数组更新数据,顺序介绍这一巧妙的神器。函数

(1)lowbit()函数spa

Lowbit()函数是对整数的位操做,返回一个二进制数的最低位“1”。如10(10)=01010(2),返回最低位的“1”,即2lowbit()函数不是库函数,须要本身定义。blog

int lowbit(int x)索引

{it

  return x&-x;原理

}遍历

原理(10为例):二进制

运用计算机的补码运算规则:原码取反+1方法

原码取反后,各位与原二进制数相反,lowbit()之后全变为1

原码:01010

反码:00101

反码加一后,二进制的属性致使各位都受影响(从最低位向高位进一,直到遇到一个0位变为1)

补码:00110

此时,lowbit位前各位与原码相反,lowbit位相等,lowbit位后全为0

将补码与原码进行&()运算,即可获得只有lowbit位为1的二进制数。

原码:01100

补码:00110

结果:00100(与运算后)

int lowbit(int x)

{

  return x&(x&(x^(x-1));

}

亦可实现返回最低位“1”,读者可自行推导。

(2)树状数组

如图所示为树状数组的表示形式,下面给出树状数组的定义: Cn=a(n-a^k+1)+......+a(n)(kn的二进制表示中从右向左数的0的个数,a^k可用lowbit函数求出)。到此,读者也许在思考此图划分的缘由吧。这正是本人思考了好久的点,下面经过数组索引(下标)的意义来理解这种结构。

(3)树状数组索引的意义

在图中咱们能够看到树状数组c的索引与要求和的数组a的索引是一一对应的。暂且理解数组c的下标意义为对数组a求和的元素数量。

c[1]=a1(a数组第1个数,下同)            1:0001

c[2]=a1+a2                                       2:0010

c[3]=a1+a2+a3                                 3:0011

c[4]=a1+a2+a3+a4                           4:0100

c[5]=a1+a2+a3+a4+a5                      5:0101

c[6]=a1+a2+a3+a4+a5+a6                6:0110

c[7]=a1+a2+a3+a4+a5+a6+a7          7:0111

c[8]=a1+a2+a3+a4+a5+a6+a7+a8    8:1000

可见每一位索引对应的处理元素个数均可经过索引二进制表示后不断进行lowbit操做求得:7=0001+0010+0100=1+2+4。而lowbit位之前的非零位则是已经处理过的。为了实现与lowbit前非零位的衔接((2)中的Cn公式),索引对应的元素在被操做数组a中的操数量即为索引的lowbit。即当前处理位置减去已经处理过的元素数量。以前的计算结果可经过不断进行n-=lowbit(n)获得(n为当前索引值)

在此以c[7]c[8]为例(此例中索引为在数组的位序,即下标从1开始)

c[7]=a[7-lowbit[7]+1]+....+a[7].此处两索引相等,为a[7].

7-lowbit(7)=6

C[6]=a[6-lowbit(6)+1]+....+a[6]=a[5]+a[6]

6-lowbit(6)=4

C[4]=a[4-lowbit(4)+1]+.....+a[4]=a[1]+a[2]+a[3]+a[4]

 4-lowbit(4)=0,结束。

c[8]=a[8-lowbit(8)+1]+...+a[8]=a[1]+a[2]+...a[8]

8-lowbit(8)=0,结束。

由此能够结合树状数组下标的意义理解上图的结构。

(4)求数组的和:

int Sum(int n)

{

  int sum=0;

  while(n>0)

  {

  sum+=c[n];

  n-=lowbit(n);/*此处即可看出时间复杂度O(lgn)的由来。由于每次lowbit运算都是对原操做数除以二。*/

  }

  return  sum;

}

(5)被操做数组更新数据(a中第i个数加x)

此时能够更好的体现树状数组的优点:O(lgn)

void change(int i,int x)

{

  while(i<=n)

  {

  c[i]=c[i]+x;

  i+=lowbit(i);

  }

}

 未获得博主容许转载者,不追究任何责任,欢迎你们给出更好的理解方式。

相关文章
相关标签/搜索