(原)数据结构之树状数组详解

树状数组

树状数组是一个查询修改复杂度都为log(n)的数据结构。

主要用于数组的单点修改&&区间求和.

另外一个拥有类似功能的是线段树

🔺树状数组(Binary Indexed Tree) ------解决动态前缀和问题的数据结构

 

1、对于询问操作(复杂度为logN)

 查询前缀和

(1)首先了解管辖区域:设节点编号为x,那么这个节点管辖的区间为2^k,(其中k为二进制末尾0的个数)个元素;

什么意思呢;

 

 

例:1)设我们一共有n个元素:a1,a2,a3,.........an,设管辖数组为d[ ] ;

则    d[6] = a5 + a6;    这是为什么呢;

看6的二进制形式为110   末尾有一个零 ,则管辖的区间为2^1 = 2个元素

 

2)再看d[8] = a1+a2+....+a8;  共8个元素  

因为8的二进制为1000  ,末尾有三个零,则管辖的区间为2^3 = 8 个元素;

 

以下面这幅图为例

 

 

(2)了解如何查询前缀和:

例:假设我们要查13这个位置的前缀和:

那么怎么知道为多少呢。

实际上就查询这三部分,那么这三部分是怎么来的

将13 化为2进制

1101 = 13;   管辖2^0 = 1个元素

现将末尾的1去掉变成

1100 = 12;管辖2^2 = 4个元素;

再将末尾的1去掉变成

1000 = 8;管辖2^3 = 8个元素

 

加起来刚好也是13个元素;

实际上13的前缀和为d[13]+d[12]+d[8] (这里也可以验证一个区间内的元素不会超过logn个,所以单次查询的复杂度最多为logn)

所以实际上如果要求这个我们就需要知道最低位的1在哪,将其减掉。

(3)了解lowbit函数(这是一个求最低位1的函数)

代码写起来非常简单,如下:

1 int lowbit(int x)
2 
3 {
4     
5     return x&(-x);
6     
7 } 

为什么是这样写呢:x与其负数;

首先从二进制的负数说起

在二进制中负数是以补码形式存在的

例   假设我们求12的最低1的位

12 的二进制为   1100;

其负数为  取反+1    :0011+1 = 0100  

将两个相与变成   1100&0100 = 0100 便找到最低位的1 了;这样我们就可以实现刚刚上面的过程了。

 

🔺所以上面的查询功能的代码可以这样写

代码如下:

 1 int lowbit(int x)
 2 {
 3     
 4     return x&(-x);    
 5 } 
 6 
 7 int Query(int x)
 8 {
 9     int ans = 0 ;
10     while(x)
11     {
12         ans += d[x];
13        x -= lowbit(x);
14     }15 }

2、对于修改操作(复杂度为logN)(复杂度分析和上面一样)

 (单点修改)

其实是上面操作的逆操作

 

 

还是以上面那幅图来说

 

 

 假设我们要修改6,那么对于应该修改图中的标红部分,应该都有贡献

也就是图中的6,8,16;那么这是怎么算出的呢,这其实是上面的逆操作;

 

以二进制来看,

6 = 110;    末尾的0为1个 所以,2^1 = 2;

 6+2  = 110 + 010 = 1000 = 8;

8 的末尾0有2个, 所以 2^3 = 8 ;

所以下一个数为 

1000+1000 = 8+8 = 16;

其实就是上面的逆过程;

所以修改6的话,应该修改的数有 6, 8,16;

 

代码如下:

 1 int lowbit(int x)
 2 {
 3     
 4     return x&(-x);    
 5 } 
 6 
 7 void Update(int x ,int value)//对a[x]加上value;
 8 {
 9     while(x<=n)
10     {
11         d[x] += value;
12         x += lowbit(x);
13     }
14 }

 

posted @ 2019-05-02 13:33  叶坚持  阅读(601)  评论(1编辑  收藏  举报