无意间看到树状数组,查了很多资料被各种图和公式绕晕了,下面记录一点个人理解。

假设数组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 + "";
    }

打印结果

clipboard.png

假设求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://www.cppblog.com/menjit...


diert
1 声望0 粉丝