首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >高级数据结构:树状数组

高级数据结构:树状数组

作者头像
Here_SDUT
发布于 2022-08-08 11:38:50
发布于 2022-08-08 11:38:50
1.7K00
代码可运行
举报
运行总次数:0
代码可运行

树状数组

1.背景

讨论树状数组前我们先来思考一个问题,有一个长度为 n 的数组,有两种操作:修改某个数的值和输出下标为 ij 的每个数的和。

  • 暴力做法:单点修改: O(1) ,区间查询:O(n)
  • 前缀和思想:单点修改: O(n) ,区间查询:O(1)

对于这个问题的两个操作似乎是鱼和熊掌不可兼得一般——想要修改快,查询就慢;想要查询快,修改就慢。这时候你可能就会说了,线段树不就好了,但是线段树太繁琐了,我们有一个更好的工具:树状数组,它使得修改和查询都是 O(logn)级别,可谓是中庸思想的典范。

2. 思想

先介绍一个函数 lowbit(x),其用途是找出 x 的二进制表示中最低位的1的十进制,表示如 :

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
lowbit(12) = 4 // 12 = 1100, 最低位的1为100,十进制为4
lowbit(10) = 2  // 10的二进制为1010,最低位的1为 10 ,十进制为2
lowbit(9) = 1  // 9的二进制为1001,最低位的1为 1, 十进制为1

lowbit函数的实现也及其简单,int lowbit(x) {return x & -x;} ,具体原理涉及到计算机组成原理的部分知识(计算机负数的表示以及补码相关知识),不做详细介绍,记住就行。

树状数组主要运用到了位运算、倍增、前缀和的思想,就是将数组的前缀和拆分成几段,使得修改某个数的时间变快了,以长度为16的数组a[] 为例:

建立一个数组 c 来存储,c[x] 保持序列 a 的区间 [x - lobit(x) + 1, x] 中所有数字的和,如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
c[4] = a[1] + a[2] + a[3] + a[4] 
c[12] = a[9] + a[10] + a[11] + a[12] // lowbit(12) = 4

数组c就是上图中所有的长方形,可以看成一个树形结构:

  • 最下面一行的数组a,代表叶子节点,存储每个数的值。
  • 每个非叶子节点用数组c表示,c[x] 存储以它为根的所有子树中所有叶节点的和。
  • 除树根外,每个非叶节点c[x] 的父节点为 c[x + lowbit(x)]
  • 每个节点c[x] 的子节点的个数等于 lowbit(x), 如c[4] 有3个子节点,因为 lowbit(0100) = 100(二进制表示),有三位。

其实不用太过于纠结细节内容,只需要理解下面的代码实现就行了,树状数组属于思想巨难但是代码很简单的东西。

由于树的层数最多是 logn 层这种方法查询和修改的时间复杂度都是 O(nlogn) 的。

3. 实现

  • 查询前缀和
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int ask(int x) {
    int sum = 0;
    for(; x; x -= lowbit(x)) sum += c[x];
    return sum;
}

上述代码表示求 a[1] 到 a[x] 的所有值,比如求a[1] 到 a[7] 的值,其实就是求c[7] + c[6] + c[4] ,写成二进制的形式:c[0111] + c[0110] + c[0100] ,可以看到就是每次减掉一个最末尾的1,而其实现方式就是 -lowbit(x), 这里结合上面的图更好理解。

  • 单点修改
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void add(int x, int val) {
    for(; x <= n; x += lowbit(x)) c[x] += val;
}

上述代码表示将a[x] 加上val,n表示a数组的长度,由于c数组维护的是前缀和,所以要修改所有包含a[x] 的节点,其实现方式其实就是不断找x的父节点直到找到树根或者超过树根(x > n)。例如a[5] + val,就是要将 c[5], c[6], c[8], c[16] 都加val,下标转换成二进制分别为:0101,0110, 1000, 10000,从左到右其实就是不断地 + lowbit(x)实现。

  • 初始化

针对一个原始数组a构造一个树状数组,一般就是先建立一个全为0的数组c,然后对于每个位置x,执行 add(x,a[x])即可。

4. 拓展

4.1 区间修改+单点查询

树状数组只能进行单点修改+区间查询的操作,我们可以利用差分思想将区间修改+单点查询的操作转换成单点修改+区间查询。定义差分数组 b[i] = a[i] - a[i-1] //a[i]为原数组, 那么 a[i]=ij=1b[i] ,即 a[i] 其实就是数组b的1到 i 的前缀和,这样就把 a[i] 的单点查询变成了 b[i] 的区间查询。对于a数组的区间修改,如果要将a[L] 到 a[R] 的值都 +val ,那么只要进行 b[L] + val , b[R+1] - val即可,这样就把区间修改变成了单点修改。

4.2 区间修改+ 区间查询

还是利用差分的思想,区间修改的做法和上面一样,至于区间查询,其实只要解决如果计算a数组的前缀和就解决了区间查询的问题了。对于a数组的前x个数的和,我们可以列出公式: xi=1a[i]=xi=1ij=1b[i]=xi=1(xi+1)b[i]=(x+1)xi=1b[i]xi=1ib[i],图示推导过程如下:

以x = 8为例,红色部分为所求的 a[1] 到 a[8] 的所有值,我们将其用绿色部分补充成一个正方形,整个正方形的值就是 (x+1)xi=1b[i] ,绿色部分的值我们发现有一定的规律,就是1b[1]+2b[2]+3b[3]+4b[4]+xb[x] ,即:xi=1ib[i] ,两者相减就是红色部分,即: (x+1)xi=1b[i]xi=1ib[i] 。所以我们只要对 b[i] 和 i * b[i] 进行树状数组的维护,就可以解决区间查询的问题了。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-4-05 1,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
原来树状数组可以这么简单?
首先最容易想到的方法就是先求出前缀和sum[i],然后区间[a,b]的和就可以直接通过sum[b]-sum[a-1]得到。
小K算法
2022/06/09
4010
原来树状数组可以这么简单?
数据结构--树状数组
树状数组的核心函数lowbit(int m):作用是求出 m 的二进制表示的末尾1的位置,对于要查询 m 的前缀和,m = m - lowbit(m) 代表不断对二进制末尾1进行-1操作,不断执行直到 m == 0 结束,就能得到前缀和由哪几个Cm构成
Michael阿明
2020/07/13
2K0
数据结构--树状数组
京东开发团队带您一起深入理解树状数组
树状数组(BIT, Binary Indexed Tree)是简洁优美的数据结构,它能在很少的代码量下支持单点修改和区间查询,我们先以 a[] {1, 2, 3, 4, 5, 6} 数组为例建立树状数组看一下树状数组的样子:
通信行业搬砖工
2023/10/17
2830
京东开发团队带您一起深入理解树状数组
树状数组-从入门到拓展(转载非原创)
转载来源:https://www.cnblogs.com/AKing-/p/15311440.html
xlj
2021/09/20
4720
数据结构之树状数组
如果我们要求一个数组内任意区间的和,最朴素的算法是每次对区间所有元素进行求和运算,时间复杂度为O(n)。也可以考虑用前缀和的方式去实现,求和运算的时间复杂度为O(1),但这样一来,如果对数组的某一项进行修改,则要同步维护前缀和数组,这会导致更新操作的时间复杂度由原来的O(1)提升为O(n)。如果数据量非常巨大,这样的时间复杂度仍然是不被接受的。
兜兜转转
2023/03/06
1K0
数据结构之树状数组
树状数组学习笔记
树状数组或二叉索引树(Binary Indexed Tree),又以其发明者命名为 Fenwick 树
EmoryHuang
2022/10/31
4750
树状数组学习笔记
算法学习笔记(2)树状数组【转】
树状数组(Binary Index Tree, BIT)也是很多OIer心中最简洁优美的数据结构之一。最简单的树状数组支持两种操作,时间复杂度均为 :
Chuanrui 初见之旅
2022/11/14
4510
算法学习笔记(2)树状数组【转】
树状数组、线段树与RMQ
binary index tree 来自OI-wiki的图,我记得刘汝佳书里也有,不过那本书不在我手边
千灵域
2022/06/17
7220
树状数组、线段树与RMQ
深入理解树状数组
树状数组(Binary Indexed Tree(BIT), Fenwick Tree)是一个查询和修改复杂度都为log(n)的数据结构。主要用于查询任意两位之间的所有元素之和,但是每次只能修改一个元素的值;经过简单修改可以在log(n)的复杂度下进行范围修改,但是这时只能查询其中一个元素的值(如果加入多个辅助数组则可以实现区间修改与区间查询)。 百度上给出了令人难以理解的概念,其实这个东西我也是琢磨了一天,参考了大量博客的笔记才搞清楚了大致思路和原理,说说心得吧! 假设数组a[1..n],那么查询a[1]
Angel_Kitty
2018/04/08
5890
深入理解树状数组
树状数组
树状数组即二叉索引树,是使用数组模拟树形结构的一种数据结构,可用于计算前缀和和区间和(元素全为1时可用来计数)。采用数组而不是直接建树来解决问题是由于某些特定问题比如区间求和完全可以不建树就能解决,这样实现简单,复杂度低。这点上和Trie树有异曲同工之妙。
Steve Wang
2020/11/03
1.7K0
树状数组
树状数组初探
在前一篇文章:线段树初探 中我们看了一下线段树的基本思想并且知道了线段树擅长于解决区间问题。其实对于某些区间问题,我们不仅可以用线段树解决,还可以用树状数组解决。那么可能有小伙伴要问了,那既然线段树和树状数组都可以解决某些区间问题,那么我就一直用线段树就好了啊,为什么还要学树状数组呢?对于这个问题,我这里能给的答案是:对于两者都能解决的区间问题,两者所用的时间复杂度都是O(logn),树状数组所用的内存空间比线段树更小,还有一个点是:实现树状数组的代码会比线段树的代码更少也更简单。下面来看一下树状数组的基本思想:
指点
2019/01/18
1K0
树状数组初探
『ACM-算法-数据结构』信息竞赛进阶指南--树状数组 (模板)
写在前面: 我们是主要是讲算法模板,即实现的代码,并不讲实现的原理 什么是树状数组? 树状数组(Binary Indexed Tree(B.I.T), Fenwick Tree)是一个查询和修改
风骨散人Chiam
2020/10/28
7160
树状数组解析
prefixSum(idx):求从数组第一个位置到第idx(含idx)个位置所有数字的和。
Tim在路上
2020/09/10
9790
树状数组理论基础
  树状数组(binary indexed trees,二进制索引树),最早由Peter M. Fenwick于1994年以“A New Data Structure for Cumulative Frequency Tables"为题发表在SOFTWARE PRACTICE AND EXPERIENCE。其初衷是解决数据压缩里的累积频率(cumulative frequency)的计算问题,现多用于高效计算数列的前缀和(∑a[i]).它可以以O(㏒n)的时间得到(∑a[i]),并同样以O(㏒n)的时间执行对某项加一个常数的操作。
用户2965768
2018/08/30
4200
树状数组理论基础
树状数组的区间修改与查询
我们知道树状数组是支持单点修改和区间查询的,但是如何进行区间修改呢? 直接进行多次单点修改的话,效率是很低的。 对于这个问题,我们可以采用差分的方式去解决 题目:POJ3468 #include <algorithm> #include <cstdio> #include <iostream> #pragma GCC optimize("O3") using namespace std; #define MAXN 100005 typedef long long ll; ll sum1[MAXN],
灯珑LoGin
2022/10/31
9670
树状数组的区间修改与查询
不容错过!这也许是全网最好的树状数组的讲解
大家好,今天给大家介绍一种新的非常非常实用的数据结构。大家学会了之后,应对各大公司的面试题以及LeetCode等网站的刷题都会用得到,也是广大acmer的入门数据结构之一。
TechFlow-承志
2021/01/07
4910
求解连续子数组和全解析-常规解法VS树状数组!
本文将介绍几求解数组前缀和和连续子数组和的三种方法,分别是遍历法、辅助数组法、树状数组法。
石晓文
2019/05/05
5590
求解连续子数组和全解析-常规解法VS树状数组!
进阶版树状数组
(y*query(tree,y)-(x-1)*query(tree,x-1))-(query(tree1,y)-query(tree1,x-1))
glm233
2020/09/28
4120
【每日基础算法】树状数组 - 动态求连续区间和
给定 n 个数组成的一个数列,规定有两种操作,一是修改某个元素,二是求子数列[a,b]的连续和。
苏州程序大白
2022/04/14
4100
【每日基础算法】树状数组 - 动态求连续区间和
什么是树状数组?让这个12岁年轻人为你讲解
现在有一个数组a,我们需要求很多次数组中不同区间的和,而且多次对a中随意一项进行更改。 比如说a = {0, 1, 5, 3, 2, 4}
小灰
2022/09/01
6770
什么是树状数组?让这个12岁年轻人为你讲解
相关推荐
原来树状数组可以这么简单?
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档