今天比赛的时候好多树状数组的题,这里总结一下树状数组的用处。
首先不得不说树状数组的思想简洁而又深刻,短短几行代码,诠释了什么叫“大道至简”,我想算法的魅力或许就在于此。
今天比赛的时候,看似简单的题总是超时,当时就敏锐的想到用树状数组解决,然而由于不太熟悉,自己又在本子上推了一遍,最后还是有几道题没来得及看,现在赶紧回来总结一下树状数组。(纯手打,不容易!)
先讲讲树状数组的用处,毕竟有了需求,才有学习的动力。
对普通数组进行M次修改或求和,时间复杂度为O(M*N),N为修改或求和需要扫描的区间大小。而对于树状数组,时间复杂度则为O(M*lgN)。加了一个lg,学过数学的我们应该都知道差距有多大。
在讲实现之前,我们需要先理解一个函数lowbit(x),这是一个自定义的函数,函数名是约定成俗的,作用就是返回x的二进制表示中最后一位1的权值
代码实现
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
写个长注释,假设x=10
10的二进制:1010,我们知道,一个数前加符号,就是用这个数的二进制取反加一。
-10的二进制:0101+1=0110
然后位运算符&(按位与),1010&0110=10,十进制表示就是2
所以lowbit(10)=2
这个函数很重要,所以先在这里交代清楚原理。
下面上图,讲讲实现树状数组(图来自百度百科)
图中有两个数组,数据都接收到底层数组a中,而数组c则是树状数组(看形状是不是很像个树)。 从图中可以直观的看到
c[1]=a[1];
c[2]=a[1]+a[2];
c[3]=a[3];
c[4]=a[1]+a[2]+a[3]+a[4];
c[5]=a[5];
c[6]=a[5]+a[6];
c[7]=a[7];
c[8]=a[1]+a[2]+a[3]+a[4]+a[5]+a[6]+a[7]+a[8];
假设结点为n,那么结点n所管辖的区间为2的lowbit(n)次方
即:c[n]=a[n-(2^lowbit(n))+1]+…..+a[n];
这样通过lowbit函数,把底层数组a和树状数组c联系了起来。
求数组a的前n位和
代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
假设n=8,lowbit(8)=8,while循环只持续了一次,得到的结果c[8]也与实际吻合
(发现没有,求前8位和,实际上只循环运算了1次!)
假设n=6,lowbit(6)=2,获取c[6]=a[5]+a[6]后,n=4;lowbit(4)=4;获取c[4]=a[1]+a[2]+a[3]+a[4];最终获取的就是a[1]+..+a[6];
(求前6位和,实际只循环运算了2次!)
还理解不了?那简单,把代码背下来,记住这个函数的返回值就是前n位和。
下面是修改,将数组a的第k位增加(或减少)num
代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
说了那么多,把我自己都讲糊涂了,本来我是直接套用函数简单粗暴,这样一写,我发现树状数组其实讲的就是二进制,