首先讲一下树状数组的作用吧,树状数组主要应用于对数据的查询、修改等维护操作,就比如有一个数组a
,下标从0
到n-1
,现在给你w
次修改,q
次查询,修改的话是修改数组中某一个元素的值;查询的话是查询数组中任意一个区间的和,在数据规模很小的时候,无论是修改某点的值还是查询某区间的和所需要的时间和空间几乎忽略不计,但当数据规模非常大时,就会有一些不可避免的弊端,如查询区间和时,需要把查询区间的数据全部遍历一遍,这时候修改的时间复杂度为O(1),查询为O(n),这时候为了节省时间,提高算法效率,就需要对算法进行优化,如果采用前缀和的方法这时修改的时间复杂度为O(n),查询为O(1),总有以部分功能是非常耗时的,但树状数组却可以完美解决这个问题。
关于树状数组的讲解在网上有许多博客介绍,但在查阅这些博客时,个人感觉这些博客讲的有一点点太理论化,对于新手来说不是非常友好,比较适合那些有一定基础的人看,于是就有了这篇博客,算是我对树状数组的个人理解。
对于树状数组,个人感觉难以理解的地方就在于不明白lowbit(x)对于x的意义,lowbit函数对于树状数组来说是非常重要的,甚至说是核心都不为过,树状数组的数据结构形似一棵树,对于树来说连接的关键是节点,节点有根节点、子节点之分,而lowbit(x)表示的就是x所在根节点下拥有的全部子节点,如下图
由以上两图可以看出,1节点所拥有的子节点为a1,1个子节点,2节点拥有的子节点为a1、a2,2个子节点,4节点拥有的子节点为a1、a2、a3、a4,4个子节点等等,往后可以看出明显的规律。
这样在进行查询操作时,只需要把k节点下的所有节点累加即可,代码例子如下:
int read(int k)//1~k的区间和
{
int sum=0;
while(k)
{
sum+=tree[k];
k-=k&-k;
}
return sum;
}
在上面代码里,通过k-=k&-k来不断跳转根节点, 实现树的遍历
修改操作,例子如下:
void add(int k,int num)
{
while(k<=n)
{
tree[k]+=num;
k+=k&-k;
}
}
在修改操作里,通过k+=k&-k操作来跳转父节点,更新所有相关节点的状态。