树状数组
树状数组(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可以这样写
int Lowbit(int x){
return x&(-x);
}
还有另一种写法
这里先给出,但是具体思路还不清楚,只知道作用是相同的
int Lowbit(int x){
return x&(x^(x-1));//^:按位异或,不同为1,相同为0
}
修改数列元素An的函数Update
若要修改An,必须修改其所有祖先,最坏情况下为修改第一个元素,最多有log2(n)个的祖先
由于An被从Tn开始的多个数组元素包含,因此需要循环添加直到树状数组边界
上面也有提到,Tn被它后面第2^k个数组元素所包含
以下是代码:
void update(int n,int number){//n为修改的数列元素Ax,number为改变数值
while(n<=along){//along为树状数组长度
T[n]+=number;
n+=Lowbit(n);//往后移动到下一个包含Ax的数组元素
}
}
获得数列A1…An和的函数Getsum
由于A1…An的和被分割为许多块,因此需要一块块加起来才能得到结果
上面也有提到,Tn包含的数列元素数为2^k,即Lowbit(n),而从A1到An一共包含n个数列元素
可以这么理解,从Tn开始,每次获得Lowbit(当前格)个元素,因此要向前跨Lowbit(当前格)格,直到走到T1之前(获得所有元素)。
以下是代码:
int Getsum(int n){
int sum=0;//sum为A1…An的和
while(n>0){
sum+=T[n];//添加当前的一块
n-=Lowbit(n);//往前跨Lowbit(n)格
}
return sum;
}
n –= Lowbit(n)实际上等价于将n的二进制的最后一个1减去。而n的二进制里最多有⌈log2(n)⌉(向上取整)个1,所以查询效率是log2(n)的。
==============
以求A1…A7的和为例
得出结果为T7+T6+T4只要三个数相加
而如果用普通数组直接保存数列A,要求A1…A7的和需要七个数相加;如果普通数组保存A的前n项和,每次修改An都要改动n个数组元素
因此在需要多次求数组某一段的和时,使用树状数组保存可以有效提高效率