树状数组 详解

首先讲一下生什么是树状数组:

由图可知,原始的数组是a数组,树状数组是e数组。通俗的说:

e[1]=a[1];

e[2]=a[1]+a[2];

e[3]=a[3];

e[4]=a[1]+a[2]+a[3]+a[4];

e[5]=a[5];

e[6]=a[5]+a[6];

e[7]=a[7];

e[8]=a[1]+a[2]+a[3]+a[4]+a[5]+a[6]+a[7]+a[8];

为什么是这样的规律呢?这就涉及到2进制的问题了。我们首先看一下e[1],1的二进制是1,有从后往前有0个连续的0,那么,我们规定:e[1]就是从a数组的1位置往前推2^0个数的和。我们再来看一下e[8],的二进制位1000,从后往前有3个连续的0,那么e[8]就是从a数组的8位置往前推2^3个数的和,即:a[1]+a[2]+a[3]+a[4]+a[5]+a[6]+a[7]+a[8]。我们最后看一下e[10],10的二进制是1010,从后往前有1个连续的0,那么e[10]就是从a数组的10位置往前推2^1个数的和,即:a[9]+a[10]。

接下来开始讲树状数组的2种基本操作:

sum操作:

求数组的前缀和。

代码如下:

int sum(int q)//求前缀和(a数组n位置到1位置上的数的和)
{
    int ret=0;
    for(int i=q;i>=1;i-=lowbit(i))
        ret+=c[i];
    return ret;

}


update操作

更新后缀和

代码如下:

void update(int i,int j)//当a数组i位置上的数变化了j后,重新更新树状数组c
{
    for(int k=i;k<=n;k+=lowbit(k))
        c[k]+=j;

}

下面是一个完整树状数组程序的代码:

#include<bits/stdc++.h>
#define MAXN 9999
using namespace std;
string op;//指令
int a[MAXN];//原始数组
int c[MAXN];//树状数组
int n;//原始数组的大小
int q;//查询前缀和时查询从数组1到q位置上的和
int z,l;//更新后缀和时原始数组z位置变化了l
int lowbit(int n)//假如说n等于8,那么8的2进制为:1000。函数返回的是2^3(二进制中有从后往前连续的3个0),即:8。
{
    return n&(-n);
}
int sum(int q)//求前缀和(a数组n位置到1位置上的数的和)
{
    int ret=0;
    for(int i=q;i>=1;i-=lowbit(i))
        ret+=c[i];
    return ret;
}
void update(int i,int j)//当a数组i位置上的数变化了j后,重新更新树状数组c
{
    for(int k=i;k<=n;k+=lowbit(k))
        c[k]+=j;
}
int main()
{
    while (cin>>n)
    {
        memset(c,0,sizeof(c));
        memset(a,0,sizeof(a));
        for(int i=1;i<=n;i++)
        {
            cin>>a[i];
            update(i,a[i]);//刚开始建立树状数组时,每次都得进行这个过程,和建立大根堆(小根堆)时差不多,每次都得进行heapinsert过程
        }
        cin>>op;
        if(op=="update")
        {
            cin>>z>>l;
            update(z,l);
        }
        if(op=="sum")
        {
            cin>>q;
            cout<<sum(q)<<endl;
        }
    }

}

接下来讲一下为什么用树状数组进行查询前缀和,更新数组中的值这些操作会很快。

我们先想一下正常的求数组前缀和的操作:从数组i位置往前遍历到1位置,累加。时间复杂度为O(n)。用树状数组:假如说求数组前6项的和,只需要找到e[6]位置和e[4]位置,累加。就得到结果了。时间复杂度为O(log n)。很显然快。

还是上面思路,我们先想一下正常的更新数组中某个位置的值的操作:找到这个位置,更新。时间复杂度为O(1)。用树状数组:假设数组1位置上的数发生了变化,我们需要更新e[1],e[2],e[4],e[8](假设数组大小是8),时间复杂度为O(log n)。比正常的操作的时间复杂度还高,但是你要两种操作合起来看,树状数组的整体时间复杂度是O(log n),而普通的方法是求前缀和操作很慢,更新数组中数的操作很快。还是选择树状数组比较好

猜你喜欢

转载自blog.csdn.net/qq_40938077/article/details/80424318