无意间看到树状数组,查了很多资料被各种图和公式绕晕了,下面记录一点个人理解。
假设数组a[0],a[1],a[2],.....,a[n],记0-m元素之和为sum(m) (0=<m<=n),我们有两种简单的方法
- 遍历累加0-m 时间复杂度O(m),空间复杂度O(1)
- 增加辅助数组s[0],s[1],s[2],.....,s[n] 时间复杂度O(1),空间复杂度O(n)
当我们频繁的求s(m)时,第一种方法不适用,当我们频繁的修改数组a时,第二种方法每次都要修改数组s,修改数组s的时间复杂度为O(n)
而树状数组可以很好解决这种场景,它类似方法二
方法二中我们定义s[m] = a[m]+a[m-1]+a[m-2]+....+a[0]
树状数组中我们定义s[m]= a[m-1]+a[m-2]+....+a[i-2^k],其中k为m的二进制的第一个1前0的个数
ps:有些资料是m至i-2^k+1,那是数组下标从1开始计数的,这里下标从0开始,因此有所区别
举个例子:10的二进制1010,因此k=1;24的二进制11000,因此k=3
我们记lowbit(m)=2^k,即有lowbit(10)=2,lowbit(24)=8
可以发现2的二进制是10,8的二进制是1000,因此lowbit的实现很简单
static int lowbit (int x){
return x&(-x);
}
如果这方法无法理解,建议看下二进制补码
接下来数组s就有的一个公式
s[m]=a[m-1]+a[s-2]+....+a[i-lowbit(m)]
我觉得树状数组讲到这里就可以了,为了便于理解,我举一个例子,测试代码如下
public static void main(String[] args) {
for (int i = 0; i < 30; i++) {
System.out.println("i = " + intFixedString(i) + " binary i = " + intBinaryString(i) + " lowbit = " + lowbit(i) + " i-2^k = " + (i - lowbit(i)));
}
}
public static String intBinaryString(int i) {
return Integer.toBinaryString((i & 0xFF) + 0x100).substring(1);
}
public static String intFixedString(int i) {
return i < 10 ? i + " " : i + "";
}
打印结果
假设求sum(28),我们分析一下 i = 28 binary i = 00011100 lowbit = 4 i-2^k = 24
s[28]=a[27]+a[26]+....+a[24]
其中最后一个数是24,继续分析 i = 24 binary i = 00011000 lowbit = 8 i-2^k = 16
s[24]=a[23]+a[22]+....+a[16]
最后一个数是16,继续分析 i = 16 binary i = 00010000 lowbit = 16 i-2^k = 0
s[16]=a[15]+a[14]+....+a[0]
很容易发现sum(28)=a[28]+s[28]+s[24]+s[16],代码实现如下
public int sum(int m) {
int sum = a[m];
while (m >= 0) {
sum += s[m];
m = m - lowbit(m);
}
return sum;
}
上面只是我的个人理解,如有错误,敬请指正。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。