解析树状数组

引入

已知一个数列,你需要进行下面两种操作:

  • 将某一个数加上 xx (修改)

  • 求出某区间每一个数的和 (查询)

如何解决这个问题呢?

如果直接使用数组来做,那么修改操作复杂度是 O(1) ,查询复杂度是 O(N).
而如果使用前缀和数组来做,那么修改操作复杂度是 O(N) ,查询复杂度是 O(1).

上面两种方法的总复杂度都是 O(N)

那么有没有更快的方法呢?有,树状数组便是一种解决方法,它的复杂度是 O(logN) (在后面我们会说明为什么)

原理

树状数组,是基于二进制的数位的特性的数据结构。所谓的特性指的是什么呢?简单地说就是可以逐位拆解出 1 ,直到整个串被如此表示。

举个例子:给出一个二进制串 100101 ,它可以被分解为 100000+100+1

这样便为求前缀和提供了一条道路:按照拆出来的数分块求前缀和。

比如说要求数组前 12 个元素的和,利用 12(10)=1100(2)=1000(2)+100(2)=8(10)+4(10)
先求出后 4 个元素的和(即 912 ),再求出除了这 4 个元素之外后 8 个元素的和(即 18 )再把它们加起来即可。

这样我们便解决了求前缀和(查询)操作。

讲到这里,我想引入这张图片帮助理解:(我们记 x 所对应的区间为 c[x])这张图完美地解释了 x 能够维护 lowbit(x) 个元素 ( lowbit(x)x 在二进制下最后一个 1 以及它后面所有的 0 构成的二进制串所对应的数,比如说 lowbit(6)=lowbit(110(2))=10(2)=2

下面来讲讲更新操作:

核心问题便是:一个数改变了,需要改变什么相关的区间?
答案是:改变维护的区间包括这个数的区间,还是举例来说,如果 a[9] 改变了,由上图可以看出,c[9],c[10],c[12]12 之后的不考虑 )均要改变。事实上,c[x]上面的区间是c[x+lowbit(x)] (比如 9(10)=1001(2) 于是 9 上面一层区间便是1001(2)+lowbit(1001(2))=1010(2)=10(10)) 依次类推)。

这样我们便知道如何更新了。

代码实现

//修改 p指的是当前位置,k指的是加上k(如果tree[p]=k则是修改为k)
void modify(int p,int k){
    for(;p<=n;p+=lowbit(p)) tree[p]+=k;
}

//查询
int query(int p){
    int res=0;
    for(;p;p-=lowbit(p)) res+=tree[p];
    return res;
}

复杂度分析

结合代码,复杂度分析可以更便于理解:
查询为例,复杂度无疑取决于那个for循环会执行多少次。根据lowbit的定义,
即使p所对应的二进制串都是 1,也不过是循环 1 的个数次。
具体地说,N=111...111(2)(有 x1 ),而 N 可以近似看成是 2x ,故循环的次数为 x=logN,
故对应的复杂度是 O(logN)
类似的,修改的操作亦然。

posted @   HinanawiTenshi  阅读(179)  评论(0编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示