树状数组(二进制索引树)
给定n个数字s=[x1,x2,x3,…,xn],求区间s[p,q)(其中1≤p<q≤n)上的所有成员之和(简称区域和/区间和)
i=p∑q−1xi 其中0≤p<q<n。该区间可以修改任意元素的值。
区间上任意位置s[i]的值可以修改,但所有元素都恒为非负整数。
循环累加数组的算法和Segment Tree算法都可以解决该问题,Fenwick树(树状数组/二进制索引树)与Segment Tree同样在时间复杂度O(log2n)内求出某个区域上的和,但空间复杂度更低。实际上由于字节操作,FenwickTree不但占用内存少,且速度更快(内存更紧凑的软件速度更快)。
设sum(n)=∑i=1ns[i],那么s[p,q)的区间和可以表示为sum(q−1)−sum(p−1)。本问题可以转化为求区间s上前n个元素之和的问题。
任意非负整数都可以表示为2的次方和,比如3=21+20,7=22+21+20,8=23。所以任意非负整数可以用一个代表bit的数组表示:3=[0,0,1,1],7=[0,1,1,1],8=[1,0,0,0],即二进制数字0011,0111,1000。那么区域s可以用n个二进制数组来表示所有数字,用Fenwick树结构来表示:
引入最低有效位lowbit函数来构造Fenwick树上的每个节点。lowbit是一个非负整数的二进制数字中,最低的非0位的数字(比如lowbit3=1,lowbit6=2,lowbit8=8,lowbit11=1,显然奇数有lowbitord≡1):
令Fenwick树上的节点x存储值
t[x]=i=x−lowbit(x)+1∑xs[i] 比如
t[1]t[2]t[3]t[4]t[5]t[6]t[7]t[8]t[9]t[10]t[11]t[12]t[13]t[14]t[15]t[16]⋯=∑i=1−1+11s[i]=∑i=2−2+12s[i]=∑i=3−1+13s[i]=∑i=4−4+14s[i]=∑i=5−1+15s[i]=∑i=6−2+16s[i]=∑i=7−1+17s[i]=∑i=8−8+18s[i]=∑i=9−1+19s[i]=∑i=10−2+110s[i]=∑i=11−1+111s[i]=∑i=12−4+112s[i]=∑i=13−1+113s[i]=∑i=14−2+114s[i]=∑i=15−1+115s[i]=∑i=16−16+116s[i]=∑i=11s[i]=∑i=12s[i]=∑i=33s[i]=∑i=14s[i]=∑i=55s[i]=∑i=56s[i]=∑i=77s[i]=∑i=18s[i]=∑i=19s[i]=∑i=910s[i]=∑i=1111s[i]=∑i=912s[i]=∑i=1313s[i]=∑i=1314s[i]=∑i=1515s[i]=∑i=116s[i]=s[1]=s[1]+s[2]=s[3]=s[1]+⋯+s[4]=s[5]=s[5]+s[6]=s[7]=s[1]+⋯+s[8]=s[9]=s[9]+s[10]=s[11]=s[9]+⋯+s[12]=s[13]=s[13]+s[14]=s[15]=s[1]+⋯+s[16]x=1x=2x=3x=4x=5x=6x=7x=8x=9x=10x=11x=12x=13x=14x=15x=16 那么Fenwick树上每个节点i覆盖到的区域和如图所示:
设数字x用2的次方和表示为:
x=[bit1,bit2,…,bitm] 比如6=[4,2],8=[8],10=[8,2]。那么恰好有
sum(x)=t[x]+sum(x−lowbit(x)) 求非负整数x的最低有效位分为以下几步:
(1) 减1使x的最低有效位变为0(x−1),最低有效位之前的那些0变为1,比如1000−1=111;
(2) 然后异或操作(x⊕(x−1))就可以将x的最低有效位及之前的所有位设置为1,比如1000⊕0111=1111;
(3) 最后与原x做与操作(x∧(x⊕(x−1))),结果即为最低有效位对应的数字;
lowbit函数的C++实现如下
int LowBit(int x) {
return x & (x ^ (x-1));
}
或者利用反码特性实现
int LowBit(int x) {
return x & (-x);
}
对于长度为n的区域s,构造Fenwick树的时间复杂度为O(n⋅log2n),查询区域和的时间复杂度为O(log2n),修改区域上一个值的时间复杂度为O(log2n),空间复杂度为O(n)。
二进制索引树
源码
FenwickTree.h
FenwickTree.cpp
测试
FenwickTreeTest.cpp