数据结构 之 树状数组

数据结构——树状数组
(一)定义:树状数组(Binary Indexed Tree(B.I.T), Fenwick Tree)是一个查询和修改复杂度都为log(n)的数据结构。主    要用于查询任意两位之间的所有元素之和,但是每次只能修改一个元素的值;经过简单修改可以在log(n)的复杂度下进行范围修改,但是这时只能查询其中一个元素的值(如果加入多个辅助数组则可以实现区间修改与区间查询)。
这种数据结构(算法)并没有C++和Java的库支持,需要自己手动实现。在Competitive Programming的竞赛中被广泛的使用。树状数组和线段树很像,但能用树状数组解决的问题,基本上都能用线段树解决,而线段树能解决的树状数组不一定能解决。 但相比较而言,树状数组效率要高很多,也更节约内存。
(二)主要用法:插线问点  +  插点问线。

(三)实战场:插点问线 —— 南阳理工116

                     插线问点 —— 南阳理工123

(四)插点问线:

  问题描述——假设有数组a[0..n](0 <= n <= 1000000 ),我们多次需要求特定连续区间的和a[x] + a[x+1] + ····a[y] (0 =< x<=y<=n), 测试样例组数(1<=m<=1000000),数组元素小于100。

解放方案(一) :从x 到 y 依次进行遍历,时间复杂度(  O(n) );

解决方案() :利用树状数组进行查询求和操作,时间复杂度(  O(log(n)  )

详解示意图:

                                                                                                                                                                                                                                                                                                                                                                                                                                                                 


              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]

              故a[x] + a[x+1] +···a[y] = c[y] - c[x-1] ;

这里有一个有趣的性质:
设节点编号为x,那么这个节点管辖的区间为2^k(其中k为x二进制末尾0的个数)个元素。因为这个区间最后一个元素必然为Ax,
所以很明显:Cn = A(n – 2^k + 1) + ... + An
算这个2^k有一个快捷的办法,定义一个函数如下即可:
   
 int Judge (int x) {
      return (x&(x^(x-1)));
   }

利用机器补码特性,也可以写成:
 
  int Judge (int x) {
       retrun (x*(-x));
   }

当想要查询一个SUM(n)(求a[n]的和),可以依据如下算法即可:
step1: 令sum = 0,转第二步;
step2: 假如n <= 0,算法结束,返回sum值,否则sum = sum + Cn,转第三步;
step3: 令n = n – lowbit(n),转第二步。
详见代码:
 
int Sum(int x){  
    int sum = 0;  
    while(x > 0){  
        sum = sum + c[x];  
        x = x - Judge(x);  
    }  
    return sum;  
}
 

(五)插线问点——假设有数组a[0..n](0 <= n <= 1000000 ),我们对特定连续区间每个数组元素进行同等增加或者减少操作(如对a[3]~a[6]增加X),然后对数组增加或者减少后的特定元素值进行查询。

解放方案(一) :从x 到 y 依次进行遍历,时间复杂度(  O(n) );

解决方案() :利用树状数组进行查询求和操作,时间复杂度(  O(log(n)  )

                                      

                           c[1] = a[1]

                           c[2] = a[2]

                           c[3] = c[2] + a[3]

                           c[4] = a[4]

                           c[5] = c[4] + a[5]

                           c[6] = c[4] + c[6]

                           c[7] = c[4] + c[7]

                           c[8] = a[8]

详细代码:

void build(int x,int y,int z,int length) { 
    while(x<=length) { 
        tree[x]+=z; 
        x+=Judge(x); 
    } 
//对从 X 开始的节点进行增加操作,仅需对X后的同层最小节点增加或删减
    y+=1; 
    while(y<=length) { 
        tree[y] -= w; 
        y+=Judge(y); 
    } 
//对 Y + 1 后修改元素删减,因为合理修改区间是[X-Y],而之前操作进行了集体增加,\n
//所以还应对不应该增加或减少的元素还原
}
//如 : 区间2-6 增加 10 ,使得  c[2] +10 && c[4] + 10; 如查询tree[3] = tree[3] + c[2];


                

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值