这个就是树状数组

树状数组有什么用?

树状数组主要用于快速查询任意两位之间的所有元素之和,以及修改指定位置的值,即区域查询与节点修改。多用于解决计算数列前缀和,区间和,逆序对问题。

具体一点的场景:有一个数组A,长度为N,你需要查询T次区间[L, R]和。如果你每次都是从数组A的L累加到R,那么T*O(R-L)的时间复杂度,毫无疑问时间要T(超时)了。但如果用了树状数组那么时间复杂度就是T*O(log(R-L))的时间复杂度,效率大大提高,当然这是因为我们用空间换了时间。

什么是树状数组?

在说树状数组之前,我们都知道一个数字的二进制表示是用该数字的补码表示的,例如5的二进制为0101,-5的二进制为1011,而不是1101。然后再来看一个叫做lowBit的函数。

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

lowBit函数就是一个数与其相反数做按位与运算。那么lowBit函数的返回值有什么实际的含义呢?我们看下面一个图,a数组就是原数组,t数组就是a数组的树状数组。我们不难发现a数组和t数组的长度大小是一样的,而且a[i]与t[i]是直接相连的,一一对应的。

7171e691a90147b987ca308880d47d3a.png假如我们现在再t[1]这个树节点,那我们怎么知道它的父节点的索引是多少呢?这个时候就要用到lowBit函数了,父节点的索引等于子节点的索引+lowBit(子节点的索引),即n+lowBit(n)。我们可以验证一下,t[3]的父节点的索引是4,那么可以计算出lowBit(3)==1,那么3+lowBit(3)==4,是准确无误的。

这个时候我们就清楚了,如果我们想要从一个叶子节点往该叶子节点的父节点位移的话,就需要借助lowBit函数算出父节点的索引,故lowBit函数是我们遍历树状数组的工具。

实现代码

构建(更新)树状数组

index为当前叶子节点的索引,value为对应的a[index]的值。假如a数组的numSize为4,那么树状数组的长度大小也为4。现在我们的index为1,且a[index]=a[1]=value,那么t[1]=a[1],那么与t[1]相连接的父节点也需要加上a[1]即value,t[1+lowBit(1)]=t[2],t[2]+=value,while循环过后,与t[1]有关联的父节点都加上了value。

void updata(ll index, ll value)
{
    while (index <= numSize)
    {
        tree[index] += value;// 当前节点累加value
        index = index + lowBit(index); // 前往父节点
    }
}

int numSize;
cin >> numSize;
for (ll i = 1; i <= numSize; i++)
{
   cin >> a[i];
   updata(i, a[i]);
}//可以再输入原数组的同时,更新树状数组

求前N项和

我们从之前的树状数组的图不难发现,如果我们要求a数组的前7项和,可以直接从a[1]累加到a[7],也可以t[4]+t[6]+t[7],当然,用树状数组的时间复杂度更低,所以我们选择用树状数组t来求前N项和。首先我们要加当前t[n]的值,然后通过lowBit函数向后位移。

ll getSumBeginzero(ll end)
{
    ll sum = 0;
    while (end > 0)
    {
        sum += tree[end];
        end = end - lowBit(end);
    }
    return sum;
}

求区间和

会求前N项和就会求区间和,LR区间不就是前R项和减去前L项和再加上a[L]嘛?

ll getSumLR(ll left, ll right)
{
    ll zeroToleft = getSumBeginzero(left);
    ll zeroToright = getSumBeginzero(right);
    return zeroToright - zeroToleft + a[left];
}

 最后全部代码如下:

#include <iostream>
#include <vector>
#include <algorithm>
typedef long long ll;
using namespace std;
ll numSize;
vector<ll> a(10);
vector<ll> tree(10);

void look(ll var)
{
    cout << var << " ";
}

ll lowBit(ll x)
{
    return x & (-x);
}

void updata(ll index, ll value)
{
    while (index <= numSize)
    {
        tree[index] += value;
        index = index + lowBit(index);
    }
}

ll getSumBeginzero(ll end)
{
    ll sum = 0;
    while (end > 0)
    {
        sum += tree[end];
        end = end - lowBit(end);
    }
    return sum;
}

ll getSumLR(ll left, ll right)
{
    ll zeroToleft = getSumBeginzero(left);
    ll zeroToright = getSumBeginzero(right);
    return zeroToright - zeroToleft + a[left];
}

int main()
{
    ll left, right, selectTime;
    cin >> numSize;
    for (ll i = 1; i <= numSize; i++)
    {
        cin >> a[i];
        updata(i, a[i]);
    }
    cout << "原数组:" << endl;
    for_each(a.begin(), a.end(), look);
    cout << endl;
    cout << "树状数组:" << endl;
    for_each(tree.begin(), tree.end(), look);
    cin >> selectTime;
    while (selectTime--)
    {
        // cin >> left;
        // cout << getSumBeginzero(left) << endl;
        cin >> left >> right;
        cout << "区间" << left << "到" << right << "总和:" << getSumLR(left, right) << endl;
    }
    return 0;
}

  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

HUTAC

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

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

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

打赏作者

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

抵扣说明:

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

余额充值