数据结构之树状数组 ——详解

简介:

                树状数组是一种维护前缀和、区间和的数据结构。它在原数组上添加索引,从而高效维护数据。 神奇而小巧的数据结构,树状数组的下标必须从1开始!无论是他的修改还是查询,时间复杂度永远都是 logn,适合用于做区间和,前缀和,单点修改比前缀和标准做法的修改快一些

原理:

                树状数组引入了分级管理制度,设置一个管理小组,每个管理员管理一个或多个连续的元素。 例如,数列有16个元素,分别用a[1],a[2],…,a[16]存储,管理数组为c[]。管理数组c[]是树状的,因 此称为树状数组

 索引:

                 lowbit:将一个数转化为他的二进制,返回二的二进制下原数的末尾零次方,一个数的lowbit值就是他索引的数据个数

                        eg:

                                        lowbit(5):因为十进制下的5转为二进制为101,末尾零个个数为零 , 所 以返回2的零次方,也就是一。

在计算机中二进制数采用的是补码表示, i的补码正好是i取反加1。 c[i]存储的区间长度:lowbit(i)=(-i)&i。

     lowbit代码实现:

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

 既然树状数组是通过二进制分解划分区间,那么c[i]存储的是哪些值?

1)区间长度 若i的二进制表示末尾有k个连续的0,则c[i]存储的区间长度为2 k,从a[i] 向前数2 k个元素,即c[i]=a[i-2k+1]+a[i-2k+2]+…+a[i]。

 区间长度就是i的二进制表示下最低位的1及它后面的0构成的数值。 例如i=20,其二进制表示为10100,末尾有2个0,区间长度为2 2,其实就是 10100最低位的1及其后面的0构成的数值(100)2 ,十进制为4

 2)前驱和后继 直接前驱:c[i]的直接前驱为c[ilowbit(i)],即c[i]左侧紧邻的子树的根。 直接后继:c[i]的直接后继为c[i+lowbit(i)],即c[i]的父节点。 前驱:c[i]左侧所有子树的根。 后继:c[i]的所有祖先。

 3)查询前缀和 前i个元素的前缀和sum[i]等于c[i]加上c[i]的前驱。 sum[7]等于c[7]加上c[7]的前驱,sum[7]=c[7]+c[6]+c[4]。

4)点更新 若对a[i]进行修改,令a[i]加上一个数z,则只需更新c[i]及其后继(祖先), 即令这些节点都加上z即可,无需修改其他节点。 例如,修改a[5],令其加2。 只需c[5]+2,然后c[5]的后继分别加2, 即c[6]+2、c[8]+2。

5)查询区间和 若求区间和值a[i]+a[i+1]+…+a[j],则求解前j个元素的和值减去前i1个元素 的和值即可,即sum[j]-sum[i-1]。

算法实现:

                        

 树状数组的优点是实现简单,效率高省空间。主要用于查询前缀和、区间和及点更新,对点查询、 区间更新效率较低。

前缀和:求a[1]到a[i]的前缀和,普通数组O(n),树状数组O(logn)。

区间和:求a[i]到a[j]的区间和,普通数组O(n),树状数组O(logn)。 点更新:修改a[i]加上z,普通数组O(1),树状数组O(logn)。 点查询:查找第i个元素,普通数组 O(1),树状数组O(logn) (求sum[i]-sum[i-1])。

区间更新:区间a[i]到a[j]的所有元素加z,普通数组O(n) ,树状数组O(nlogn) 。

减法规则:当问题满足减法规则时,例如求区间和a[i]到a[j],则sum(i,j)=sum[j]-sum[i-1]。

局限1:维护信息需要满足减法规则,所以无法直接维护区间最值。

局限2:对修改支持不好,单点修改可以求差值,转换为加法操作

给大家看一道模板题:

  

贴一下树状数组模板代码:

#include<bits/stdc++.h>
using namespace std;
int c[500001]={0},n;
int m,a,x,y,z;
int lowbit(int x)
{
	return x&-x;
}
void smart(int x,int z)
{
	for(;x<=n;x+=lowbit(x))
	{
		c[x]+=z;
	}
}
int body(int x)
{
	int ans=0;
	for(;x;x-=lowbit(x))
	{
		ans+=c[x];
	}
	return ans;
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a);
		smart(i,a);
	}
	while(m--)
	{
		scanf("%d%d%d",&z,&x,&y);
		if(z==1)
		{
			smart(x,y);
		}
		if(z==2)
		{
			cout<<body(y)-body(x-1)<<endl;
		}
	}
	return 0;
}

看的这么认真,不点个赞再走吗?

  • 9
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值