树状数组
树状数组(Binary Indexed Tree(B.I.T), Fenwick Tree)是一个查询和修改复杂度都为log(n)的数据结构。主要用于查询任意两位之间的所有元素之和,但是每次只能修改一个元素的值;经过简单修改可以在log(n)的复杂度下进行范围修改,但是这时只能查询其中一个元素的值(如果加入多个辅助数组则可以实现区间修改与区间查询)。 这种数据结构(算法)并没有C++和Java的库支持,需要自己手动实现。在Competitive Programming的竞赛中被广泛的使用。树状数组和线段树很像,但能用树状数组解决的问题,基本上都能用线段树解决,而线段树能解决的树状数组不一定能解决。相比较而言,树状数组效率要高很多。 ——摘自<树状数组>百度百科
设有一数列A{1…n}
用树状数组T[n]进行保存
Tn指数组第n个元素的数值,An指数列的第n个元素
如图所示
对于第n个数组元素Tn,它包含的数列元素为2^k个(其中k为n二进制末尾0的个数) 同时Tn被它后面第2^k个数组元素所包含。 比如T1就被它后面第2^k=1个数组元素,也就是T2所包含 而T2就被它后面第2^k=2个数组元素,也就是T4所包含
Lowbit函数
Lowbit函数的作用是求2^k 我们知道,二进制数的第n位(从右往左数起)表示的数值是2^(n-1) 当n为0001(十进制1)的时候,k为0,2^k为1 当n为0010(十进制2)的时候,k为1,2^k为2 当n为0011(十进制3)的时候,k为0,2^k为1 当n为0100(十进制4)的时候,k为2,2^k为4 …… 可以发现,2^k的值等于n最低位1所代表的数值 所以,Lowbit函数的作用也是取出n最低位的1
在计算机中,使用补码储存和表示数值 补码的负数相当于原码取反再加1 而补码和原码进行按位与运算时(&),得到的结果就是原码最低位1 ps:按位与运算(&)同是1为1,否则为0
因此Lowbit可以这样写
|
|
还有另一种写法 这里先给出,但是具体思路还不清楚,只知道作用是相同的
|
|
修改数列元素An的函数Update
若要修改An,必须修改其所有祖先,最坏情况下为修改第一个元素,最多有log2(n)个的祖先 由于An被从Tn开始的多个数组元素包含,因此需要循环添加直到树状数组边界 上面也有提到,Tn被它后面第2^k个数组元素所包含 以下是代码:
|
|
获得数列A1…An和的函数Getsum
由于A1…An的和被分割为许多块,因此需要一块块加起来才能得到结果 上面也有提到,Tn包含的数列元素数为2^k,即Lowbit(n),而从A1到An一共包含n个数列元素 可以这么理解,从Tn开始,每次获得Lowbit(当前格)个元素,因此要向前跨Lowbit(当前格)格,直到走到T1之前(获得所有元素)。 以下是代码:
|
|
n –= Lowbit(n)实际上等价于将n的二进制的最后一个1减去。而n的二进制里最多有⌈log2(n)⌉(向上取整)个1,所以查询效率是log2(n)的。
============== 以求A1…A7的和为例 得出结果为T7+T6+T4只要三个数相加 而如果用普通数组直接保存数列A,要求A1…A7的和需要七个数相加;如果普通数组保存A的前n项和,每次修改An都要改动n个数组元素
因此在需要多次求数组某一段的和时,使用树状数组保存可以有效提高效率