TA的每日心情 | 开心 2021-3-12 23:18 |
---|
签到天数: 2 天 [LV.1]初来乍到
|
(一) 什么是树状数组?
树状数组用来求区间元素和,求一次区间元素和的时间效率为O(logn)。 有些同学会觉得很奇怪。用一个数组S保存序列A[]的前i个元素和,那么求区间i,j的元素和不就为S[j]-S[i-1],那么时间效率为O(1),岂不是更快? 但是,如果题目的A[]会改变呢?例如:
我们来定义下列问题,我们有n个盒子。可能的两个操作为:
1.向盒子k添加石块
2.查询从盒子i到盒子j总的石块数 自然的解法带有对操作1为O(1)而对操作2为O(n)的时间复杂度。但是用树状数组,对操作1和2的时间复杂度都为O(logn)。 (二) 图解树状数组C[]
现在来说明下树状数组是什么东西?假设序列为A[1]~A[8]


网络上面都有上图,但是我将这个图做了2点改进。 (1)图中有一棵满二叉树,满二叉树的每一个结点对应A[]中的一个元素。
(2)C为A对应的那一列的最高的节点。 现在告诉你:序列C[]就是树状数组。那么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]; 以上只是枚举了所有的情况,那么推广到一般情况,得到一个C的抽象定义:
因为A[]中的每个元素对应满二叉树的每个叶子,所以我们干脆把A[]中的每个元素当成叶子,那么:C=C的所有叶子的和。 现在不得不引出关于二进制的一个规律:先仔细看下图: 将十进制化成二进制,然后观察这些二进制数最右边1的位置:
1 --> 00000001
2 --> 00000010
3 --> 00000011
4 --> 00000100
5 --> 00000101
6 --> 00000110
7 --> 00000111
8 --> 00001000 1的位置其实从我画的满二叉树中就可以看出来。但是这与C[]有什么关系呢?接下来的这部分内容很重要: 在满二叉树中,
以1结尾的那些结点(C[1],C[3],C[5],C[7]),其叶子数有1个,所以这些结点C代表区间范围为1的元素和;
以10结尾的那些结点(C[2],C[6]),其叶子数为2个,所以这些结点C代表区间范围为2的元素和;
以100结尾的那些结点(C[4]),其叶子数为4个,所以这些结点C代表区间范围为4的元素和;
以1000结尾的那些结点(C[8]),其叶子数为8个,所以这些结点C代表区间范围为8的元素和。
扩展到一般情况: i 的二进制中的从右往左数有连续的x个“0”,那么拥有2^x个叶子,为序列A[]中的第i-2^x+1到第i个元素的和。
终于,我们得到了一个C的具体定义:
C=A[i-2^x+1]+…+A,其中x为i 的二进制数中的从右往左数有连续“0”的个数。(i>=1)
(三)C展开以后有多少项?由下面公式计算:
- int lowbit(int t){//计算c[t]展开的项数
- return t&(-t);
- }
-
- 例:
- lowbit(1)=1 C1 = A1
- lowbit(2)=2 C2 = A1 + A2
- lowbit(3)=1 C3 = A3
- lowbit(4)=4 C4 = A1 + A2 + A3 + A4
- lowbit(5)=1 C5 = A5
- lowbit(6)=2 C6 = A5 + A6
- lowbit(7)=1 C7 = A7
- lowbit(8)=8 C8 = A1 + A2 + A3 + A4 + A5 + A6 + A7 + A8
- .......
- c[t]展开的项数就是lowbit(t),c[t]就是从a[t]开始往左连续求lowbit(t)个数的和,
-
-
- lowbit(1)=1 lowbit(2)=2 lowbit(3)=1 lowbit(4)=4
- lowbit(5)=1 lowbit(6)=2 lowbit(7)=1 lowbit(8)=8
- lowbit(9)=1 lowbit(10)=2 lowbit(11)=1 lowbit(12)=4
- lowbit(13)=1 lowbit(14)=2 lowbit(15)=1 lowbit(16)=16
- lowbit(17)=1 lowbit(18)=2 lowbit(19)=1 lowbit(20)=4
- lowbit(21)=1 lowbit(22)=2 lowbit(23)=1 lowbit(24)=8
- lowbit(25)=1 lowbit(26)=2 lowbit(27)=1 lowbit(28)=4
- lowbit(29)=1 lowbit(30)=2 lowbit(31)=1 lowbit(32)=32
- lowbit(33)=1 lowbit(34)=2 lowbit(35)=1 lowbit(36)=4
- lowbit(37)=1 lowbit(38)=2 lowbit(39)=1 lowbit(40)=8
- lowbit(41)=1 lowbit(42)=2 lowbit(43)=1 lowbit(44)=4
- lowbit(45)=1 lowbit(46)=2 lowbit(47)=1 lowbit(48)=16
- lowbit(49)=1 lowbit(50)=2 lowbit(51)=1 lowbit(52)=4
- lowbit(53)=1 lowbit(54)=2 lowbit(55)=1 lowbit(56)=8
- lowbit(57)=1 lowbit(58)=2 lowbit(59)=1 lowbit(60)=4
- lowbit(61)=1 lowbit(62)=2 lowbit(63)=1 lowbit(64)=64
复制代码 (四) 修改
当我们修改A的值时,可以从C往根节点一路上溯,调整这条路上的所有C[]即可,对于节点i,父节点下标 p=i+lowbit(i)- //增加某个元素的大小,给某个节点 i 加上 x
- update(int i,int x){
- while(i<=n){
- c[i]=c[i]+x;
- i=i+lowbit(i);
- }
- }
复制代码
(五) 利用树状数组求前i个元素的和S[n]
理解了C[n]后,前n个元素的和S[n]就很容易实现。 只需找到n以前的所有最大子树,把其根节点的C加起来即可。
我们拿一个具体的实例来看:
S[7]=C[7]+C[6]+C[4]
因为C[7]=A[7],C[6]=A[6]+A[5],C[4]=A[4]+A[3]+A[2]+A[1],所以S[7]=C[7]+C[6]+C[4]- int Sum(int n) //求前n项的和. 从下标1开始
- {
- int sum=0;
- while(n>0)
- {
- sum+=c[n];
- n=n-lowbit(n);
- }
- return sum;
- }
复制代码 (六)测试
- 例:
- public class TreeArray{
- int n;//数组元素个数
- int[] a;//原数组
- int[] c;//对应的树状数组
-
- public TreeArray(int[] a){
- n=a.length;
- this.a=a;
- c=new int[a.length];
- for(int i=1;i< a.length;i++){ //从下标1开始
- for(int j=0;j< lowbit(i);j++)
- c[i]=c[i]+a[i-j];
- }
- }
-
-
- private int lowbit(int t){//计算c[t]展开的项数
- return t&(-t);
- }
-
- private void update(int i,int x){
- while(i<=n){
- c[i]=c[i]+x;
- i=i+lowbit(i);
- }
- }
-
- private int Sum(int k){ //求前k项的和. 从下标1开始
- int sum=0;
- while(k>0){
- sum+=c[k];
- k=k-lowbit(k);
- }
- return sum;
- }
-
- public static void main(String args[]){
- int a[]={0,1,2,3,4,5,6,7,8,9,10}; //从下标1开始
- TreeArray ta=new TreeArray(a);
- System.out.println(ta.Sum(10));
- ta.update(5,-20);
- System.out.println(ta.Sum(10));
- System.out.println(ta.Sum(4));
-
- }
- }
-
- 运行:
- C:java>java TreeArray
- 55
- 35
- 10
复制代码
|
|