树状数组与前缀和管理

树状数组与前缀和管理

  • 树状数组的用途及优势
  • 从一个例题开始
  • 树状数组的特点
  • 数学规律
    • C[i]怎么求
    • A[i]怎么改
    • 前i项和怎么求
  • 后记

树状数组的用途及优势

树状数组主要用于解决基于区间上的更新以及求和问题,一般的方法时间复杂度为O(N),树状数组可以把时间复杂度降低到O(logN)。

从一个例题开始

我们需要对整数数组A进行维护,维护内容为:
1)修改一个元素,如A[i]=j,我们记作C(i,j);
2)对前i项求和,即求A[1]+A[2]+…+A[i],我们记作Q(i)。
维护内容刚好对应之前说的树状数组的用途。

如果直接对数组A进行操作,C(i,j)时间复杂度是O(1),Q(i)时间复杂度是O(N).

那么怎样降低Q(i)的时间复杂度呢?

我们可能会想到二叉树(不懂二叉树的可以直接跳过)

树状数组与前缀和管理_第1张图片父节点保存的是两个子节点的和,对数组A求前i项和时,可以找树上对应的节点,这样就可以降低时间复杂度了。树状数组对二叉树进行了改进,节省了储存空间,我们只需要新建一个和原数组A等长的辅助数组就行了,如下图:

树状数组与前缀和管理_第2张图片
相比二叉树,树状数组对红色框框里的节点做了约省,这可以保证树状数组的长度和原数组A长度相同。

树状数组与前缀和管理_第3张图片

树状数组的特点

树状数组与前缀和管理_第4张图片
我们记这个树状数组为C,从前面的描述可以看出来,C的每个元素的值都是与该节点通过直线直接相连的节点的和。这么说可能比较抽象,举几个例子吧!
C[1] = A[1];
C[2] = C[1] + A[2];
C[3] = A[3];
C[4] = C[2] + C[3] + A[4];
C[5] = A[5];
C[6] = C[5] + A[6];
C[7] = A[7];
C[8] = C[4] + C[6] + C[7] + A[8];
请对照图好好看看哦~

这样
Q(8)=C[8]
Q(7)=C[7] + C[6] + C[4]
可见求和操作时间复杂度降低了。

不过如果要该A的某个元素的话,就要沿着树向上该对应的父节点。
例如把A[3]的值加3,则需要把C[3],C[4],C[8]的值都加3。时间复杂度反而增加了。。。。。

数学规律

C[i]怎么求

C[i] = A[i - 2k+1] + A[i - 2k+2] + … + A[i];
k为i在二进制 下末尾0的个数
例如
i = (1000)2,则k = 3
i = (1001010110010000)2, 则k=4

令LOWBIT(i) = 2k
则LOWBIT(x) = x and (x xor (x-1) )

(请先记住这个定义)

A[i]怎么改

令P1 = i ;
Pi+1 = Pi + LOWBIT(Pi)

则需要依次修改C[p1], C[p2], …

例如修改A[3]
P1 = 3 = (11)2 ; LOWBIT(P1) = 20 = 1 ; 改C[3]
P2 = 3 + 1 = 4 = (100)2 ; LOWBIT(P2) = 22 = 4 ; 改C[4]
P3 = 4 + 4 = 8 = (1000)2 ; LOWBIT(P3) = 23 = 8 ; 改C[8]
P4 = 8 + 8 = 16 > 8, 已经超过例子中数组的长度了,结束

前i项和怎么求

令P1 = i ;
Pi+1 = Pi - LOWBIT(Pi)

则需要依次修改C[p1], C[p2], …

例如累加前7项
P1 = 7 = (111)2 ; LOWBIT(7) = 20 = 1 ; 加C[7]
P2 = 7 - 1 = 6 = (110)2 ; LOWBIT(6) = 21 = 2 ; 加C[6]
P3 = 6 - 2 = 4 = (100)2 ; LOWBIT(4) = 22 = 4 ; 加C[4]
P4 = 4 - 4 = 0 ; 结束(到0终止)

后记

俺就学到这里,扩展内容以后补充。俺也是刚学的,讲的不对的地方请大佬指正 :)

你可能感兴趣的:(算法)