树状数组是一种用于查询区间和的数据结构。
比如对于数组a[1,2,3,4,5,6,7,8,9,10],我们要知道它的前4项和,我们就需要把这前四个一个个加起来才能得到结果,也就是O(K)(前k项和)的复杂度。如果查询次数少,k也比较小还可以接受,但是在大规模的查询和数据量时,这个复杂度就太高了。树状数组就是为了解决这个问题而创造出来的,它能在log(k)的复杂度内查询区间和。
树状数组其实就是把一颗二叉树压缩在了一个数组里,看下图(一如既往的丑):
对于数组[1,2,3,4],我们把它转化为一个求和树,每个节点的值就是它的两个子节点的值,此时我们要求前k项就比较好求了,比如求前4项,我们可以直接给出节点10。但是我们如何用一个树组来表示它呢?如果要每个节点都有一个位置对应,起码得有2n个节点,但是我们只用n个。考虑b,d两个节点,其实b里面已经包含了d,所以我们可以把他们合并。同样,a里面其实包含了e,g他们也可以合并到位置4。
我们来看看一般的规律(下面的位置索引都是二进制值):
索引 | 包含值 |
---|---|
001(1) | 001 |
010(2) | 001 010 |
011(3) | 011 |
100(4) | 001 010 011 100 |
要得到从左到右第一个1也很简单 x&(-x)即可。
下面是具体代码:
#include<iostream>
using namespace std;
//Binary Indexed Tree(BIT)
//得到x只剩下从右到左第一个一时的值。
int getk(int x){
return x&(-x);
}
//由正常的数组建立树状数组
void build(int s[],int size){
for(int i=1;i<=size;i++){
int next = getk(i)+i;
if(next<=size)s[next-1] += s[i-1];
}
}
//更新一个位置的值
void update(int s[],int size,int k,int dt){
k++;
while(k<=size){s[k-1]+=dt;k+=getk(k);}
}
//求前k项和
int ksum(int s[],int size,int k){
int result = 0;
while(k>1){cout << k << endl;result += s[k-1];k -= getk(k);}
return result;
}
int main(){
int a[10] = {1,2,3,4,5,6,7,8,9,10};
build(a,10);
cout << ksum(a,10,4) << " " << ksum(a,10,7) << endl;
update(a,10,4,-1);
for(int i=0;i<10;i++){
cout << a[i] << " ";
}
cout << endl;
}