树状数组原理及实现

应用

树状数组又称二进制索引树,通过二进制分解划分区间。多用于高效的计算数列的前缀和,在查询前缀和与点更新操作上,其时间复杂度均可在O(log n)下完成。

原理

树状数组引用了分级管理制度且设置了管理小组,管理小组中的每一个成员都管理一个或多个的连续元素。通过下标进行对数组中连续元素的管理。
其逻辑结构表现如图所示:
在这里插入图片描述
其中数组a代表着数组中的原始数据,数组c表示着前n个元素的和。即c为数组的前缀和。同样,对于每个数组c中的元素值为图中箭头相连的数据之和。

前缀和查询

例如计算数组中前8个元素的和,即c[8]的值,将如下图所示:

在这里插入图片描述

c[8]只要将c[4],c[6],c[7],a[8]相加,则计算出的结果便是前八个元素的和。
但是这里的例子并没有很好的展现树状数组如何进行前缀和查询的,因为由图可以看出c[8]本身存的便是前八项的和。
那么我们可以再举例,计算前7项的数据和。
这里我们不能再去根据c[7]得到前7项的和,因为c[7]中存储的数据仅仅是a[7]的值。但是观察图可以看出,c[4]包含了前四项的和,c[6]包含了第五和第六项的和,那么再加上c[7],这样得到的就是前7项的前缀和。即如图所示
在这里插入图片描述

通过以上两个例子,我们其实可以发现,当逻辑上数组c[]形成的树形结构,其根结点中的值就是其下所有子树范围中的数据之和,若c[i]的二进制表示末尾有 k k k个连续的0,则c[i]所存储的区间长度为 2 k 2^k 2k,从 a [ i ] a[i] a[i]开始的前 2 k 2^k 2k个元素。在计算前7项数据的例子中,不难看出其实就是计算c[7]及其左侧所有树的根结点之和。

点更新

当我们需要对原始数据中某一个数据进行更新时,我们需要同时将它所贡献的所有数组c中元素进行更新。因为通过前面计算前缀和的过程,我们已经知道了,在查询时,仅对根结点进行计算,不再计算其子树中的结点值。如图
在这里插入图片描述

在对a[3]数据进行更新时,只需更新其有贡献的c[3],c[4],c[8]元素即可(数据范围在1~11)。

算法实现

区间长度

前面已经知道了 c [ i ] c[i] c[i]中的值就是其下所有子树范围中的数据之和,当 c [ i ] c[i] c[i]的二进制表示末尾有 k k k个连续的0,区间长度也就为 2 k 2^k 2k,是从 a [ i ] a[i] a[i]开始的前 2 k 2^k 2k个元素。那么如何去跳过这 2 k 2^k 2k长度加上左侧相邻的数据和呢?
因为是末尾 k k k个0,那么可以将下标 i i i按位取反并加1后与原 i i i相与,这样可以得到的是 i i i最末端1所对应的大小,也就是需要跳过的区间长度。根据整数在计算机中的存储方式,负数为补码存储,已经是完成了对其相反数的按位取反并加一。

代码
int lowbit(int x){
	return (-x)&x;
}

前驱后继

直接前驱:c[i-lowbit(i)],即c[i]左侧相邻的子树根。
直接后继:c[i+lowbit(i)],即c[i]的父节点。
前驱:c[i]的左侧所有子树的根。
后继:c[i]的所有祖宗节点。

查询前缀和

查询时,计算c[i]的前驱之和即可。

int getsum(int x){
	int ans = 0;
	for(int i = x;i;i-=lowbit(i))
		ans+=c[i];
	return ans;
}

点更新

更新时,一并更新其所有后继即可。

void add(int x,int k){//a[x]+=k;
	for(int i = x;i<=maxn;i+=lowbit(i))//maxn为数组的最大范围
		c[i]+=k;
}

区间和

当计算m~n的区间范围和时,计算出m-1的前缀和与n的前缀和大小,getsum(n)-getsum(m-1)即为区间和。

多维树状数组

仅需将上文中的得到的一维数组c[i]看做原始数据数组,再应用同样的计算方法,更新即可得到多维的树状数组,查询[0,0]到[m,n]的区间和。点更新同上。

分析

树状数组求取前缀和需要预处理,每一个数据都必须以更新的形式插入到数组中去,前n个数据的初始化需要用时 O ( n l o g n ) O(nlog n) O(nlogn),后续的查询,更新同样都是 O ( l o g n ) O(log n) O(logn)。即在区间更新与点查询中效率较低。同样的,树状数组不可以解决类似于求取区间最值的问题。

注意:树状数组下标必须从1开始,因为当下标为0时,其lowbit值为0

例题解析

POJ 2352 数星星

POJ 3067 公路交叉数

POJ 3321 子树查询

感想

对于树状数组的实现较为容易,在逻辑上理解并不存在较大困难,但是关键在与如何去将一组数据,一份题意抽取转化为树状数组可以解决的问题,树状数组混合了其它知识点后如何进行对应的分析。以及在树状数组和线段树中选择更加适合的算法进行实现。

  • 6
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

registor11

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值