前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >数据结构--树状数组

数据结构--树状数组

作者头像
Michael阿明
发布于 2020-07-13 08:20:30
发布于 2020-07-13 08:20:30
1.9K00
代码可运行
举报
运行总次数:0
代码可运行

1. 树状数组

类似数据结构线段树(Segment Tree)

树状数组 跟 线段树 的区别:

  • 树状数组能做的事情,线段树都能做!(线段树功能更牛)
  • 树状数组代码简单,实现起来比线段树容易(树状数组代码更简单)

树状数组的 查询 和 修改 复杂度都为

log(n)
  • 原数组为 A
  • 树状数组为 C(注意下标从1开始!!!
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
C1 = A1
C2 = C1 + A2 = A1 + A2
C3 = A3
C4 = C2 + C3 + A4 = A1 + A2 + A3 + A4
C5 = A5
C6 = C5 + A6 = A5 + A6
C7 = A7
C8 = C4 + C6 + C7 + A8 = A1 + A2 + A3 + A4 + A5 + A6 + A7 + A8
  • 对 C8 (8的2进制为1000,后面有3个0,所以他管着3个数C4、C6、C7,还有自己A8
  • 奇数末尾没有0,所以奇数位只有自己
  • 那怎么计算一个数管着几个数呢?

2. 单点修改

树状数组的核心函数lowbit(int m):作用是求出 m 的二进制表示的末尾1的位置,对于要查询 m 的前缀和,m = m - lowbit(m) 代表不断对二进制末尾1进行-1操作,不断执行直到 m == 0 结束,就能得到前缀和由哪几个Cm构成

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int lowbit(int m){
    return m & (-m);//(获取最后一个1的10进制值)
}
int getsum(int i){    //求A[1],A[2],...A[i]的和
    int res = 0;
    while(i > 0){
        res += c[i];
        i -= lowbit(i);
    }
    return res;
}

对8(1000)来算下: 补充:负数的二进制是 正数取反末尾+1

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
8 & (-8) = 1000 & (.111..1000) = 1000 = 8....8-8=0,sum=C8=A[1]+..A[8]
-------------
6 & (-6) =  0110 & (1010) = 10 = 2 .... 6-2=4,sum=C6
4 & (-4) = 0100 & (1100) = 100 = 4.....4-4=0,sum+=C4=C6+C4=A[1]+...A[6]
-------------
奇数上面操作结果均为 1
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <bits/stdc++.h>
using namespace std;

const int N = 8;
int a[N+1]={0,1,2,3,4,5,6,7,8}, c[N+1] = {0}; //对应原数组和树状数组

int lowbit(int x){
    return x&(-x);
}
//修改操作就是getsum的逆过程
void update(int i, int delta){    //在i位置加上delta
    while(i <= N){
        c[i] += delta;
        i += lowbit(i);
    }
}

int getsum(int i){    //求A[1],A[2],...A[i]的和
    int res = 0;
    while(i > 0){
        res += c[i];
        i -= lowbit(i);
    }
    return res;
}

int main(){
    for(int i = 1; i <= N; ++i)
        update(i,a[i]);//读取原数据,插入树状数组
    cout << getsum(3) << endl;//获取前3个数的和
    cout << getsum(8) << endl;
    update(3,2);
    cout << getsum(3) << endl;
    cout << getsum(8) << endl;
    cout << getsum(4)-getsum(2) << endl;//获取A[3],A[4]的区间和
    return 0;
}

结果:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
6
36
8
38
9

3. 区间修改

将原数组的差值A[i]-A[i-1],(A[0]=0)存入树状数组

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
A[i] = 1 2 3 5 6 9
D[i] = 1 1 1 2 1 3 (差值)

把[2,5]区间内值加上2,则变成了

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
A[i] = 1 4 5 7 8 9
D[i] = 1 3 1 2 1 1

发现只有LR+1处的值需要修改(左边+,右边-),降低了时间复杂度 所以,update修改的时候,只需要修改两个端点(因为中间的差值不变) 那么上面的 getsum(i) 的结果就是原数组的A[i],很好证明,高中数学(把D[i] 加一下也能发现)


那在上面基础上,我们获取区间的和怎么办? 先来推导一下:

A[i]=D[i]+D[i1]+...+D[1]A[i1]=D[i1]+D[i2]+...+D[1]...A[2]=D[2]+D[1]A[1]=D[1]

左右都加起来:

ix=1A[x]=iD[1]+(i1)D[2]+...+1D[i]=iix=1D[x]ix=1(D[x](x1))

看见最后两项,就明白了吧,再增加一个树状数组 存入

D[x](x1)
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <bits/stdc++.h>
using namespace std;

const int N = 8;
int a[N+1]={0,1,2,3,4,5,6,7,8}, c[N+1] = {0}; //对应原数组和树状数组
int sum1[N+1] = {0}, sum2[N+1] = {0};//对应D[i] , D[i]*(i-1)

int lowbit(int x){
    return x&(-x);
}
//------区间修改-------
void update1(int i, int delta){    //在i位置加上delta
    int x = i;//系数不能变,先存起来
    while(i <= N){
        sum1[i] += delta;
        sum2[i] += delta*(x-1);
        i += lowbit(i);
    }
}
void update_range(int l, int r, int delta)    //给区间加上delta
{
    update1(l, delta);//只需修改L,R+1,左+,右-
    update1(r+1, -delta);
}

int query_p(int i){	//A[1],A[2]...A[i]的和
    int res = 0, x = i;//系数不能变,先存起来
    while(i > 0){
        res += x*sum1[i] - sum2[i];
        i -= lowbit(i);
    }
    return res;
}
int query_range(int l, int r)	//区间[l,r]的和
{
    return query_p(r)-query_p(l-1);
}
int main(){
    cout << "区间修改,单点查询" << endl;
    for(int i = 1; i <= N; ++i)
        update1(i,a[i]-a[i-1]);//读取原数据差值,插入树状数组
    cout << query_p(3) << endl;//获取前3个数的和
    cout << query_p(8) << endl;
    update_range(3,3,2);//修改区间【3,3】,都加上2
    cout << query_p(3) << endl;
    cout << query_p(8) << endl;
    cout << query_range(3,4) << endl;//获取A[3],A[4]的区间和
    return 0;
}

运行结果

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
6
36
8
38
9

4. 完整代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * @Description: 树状数组
 * @Author: michael ming
 * @Date: 2020/4/1 23:38
 * @Modified by: 
 * @Website: https://michael.blog.csdn.net/
 */

#include <bits/stdc++.h>
using namespace std;

const int N = 8;
int a[N+1]={0,1,2,3,4,5,6,7,8}, c[N+1] = {0}; //对应原数组和树状数组
int sum1[N+1] = {0}, sum2[N+1] = {0}; //对应D[i] , D[i]*(i-1)
int lowbit(int x){
    return x&(-x);
}
//-------单点修改----------
void update(int i, int delta){    //在i位置加上delta(单点)
    while(i <= N){
        c[i] += delta;
        i += lowbit(i);
    }
}

int query(int i){    //求A[1],A[2],...A[i]的和
    int res = 0;
    while(i > 0){
        res += c[i];
        i -= lowbit(i);
    }
    return res;
}

//------区间修改-------
void update1(int i, int delta){    //在i位置加上delta
    int x = i;//系数不能变,先存起来
    while(i <= N){
        sum1[i] += delta;
        sum2[i] += delta*(x-1);
        i += lowbit(i);
    }
}
void update_range(int l, int r, int delta)    //给区间加上delta
{
    update1(l, delta);//只需修改L,R+1,左+,右-
    update1(r+1, -delta);
}

int query_p(int i){
    int res = 0, x = i;//系数不能变,先存起来
    while(i > 0){
        res += x*sum1[i] - sum2[i];
        i -= lowbit(i);
    }
    return res;
}
int query_range(int l, int r)//区间[l,r]的和
{
    return query_p(r)-query_p(l-1);
}
int main(){
    //单点修改
    cout << "单点修改,单点查询" << endl;
    for(int i = 1; i <= N; ++i)
        update(i,a[i]);//读取原数据,插入树状数组
    cout << query(3) << endl;//获取前3个数的和
    cout << query(8) << endl;
    update(3,2);
    cout << query(3) << endl;
    cout << query(8) << endl;
    cout << query(4)-query(2) << endl;//获取A[3],A[4]的区间和
    cout << "区间修改,单点查询" << endl;
    for(int i = 1; i <= N; ++i)
        update1(i,a[i]-a[i-1]);//读取原数据差值,插入树状数组
    cout << query_p(3) << endl;//获取前3个数的和
    cout << query_p(8) << endl;
    update_range(3,3,2);//修改区间【3,3】,都加上2
    cout << query_p(3) << endl;
    cout << query_p(8) << endl;
    cout << query_range(3,4) << endl;//获取A[3],A[4]的区间和
    return 0;
}

5. 参考文献

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

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

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

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

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
树状数组-从入门到拓展(转载非原创)
转载来源:https://www.cnblogs.com/AKing-/p/15311440.html
xlj
2021/09/20
4340
原来树状数组可以这么简单?
首先最容易想到的方法就是先求出前缀和sum[i],然后区间[a,b]的和就可以直接通过sum[b]-sum[a-1]得到。
小K算法
2022/06/09
3720
原来树状数组可以这么简单?
详解树状数组(C/C++)
树状数组(Binary Indexed Tree,简称BIT或Fenwick Tree)是一种用于高效处理数据序列的算法数据结构。它能够支持两个主要操作:单点更新和区间求和,这两个操作的时间复杂度都能达到O(log n),其中 n 是数据序列的长度。树状数组非常适合处理那些需要频繁更新和查询区间和的问题。
摆烂小白敲代码
2024/09/23
1350
详解树状数组(C/C++)
高级数据结构:树状数组
讨论树状数组前我们先来思考一个问题,有一个长度为 n 的数组,有两种操作:修改某个数的值和输出下标为 i 到 j 的每个数的和。
Here_SDUT
2022/08/08
1.7K0
高级数据结构:树状数组
树状数组、线段树与RMQ
binary index tree 来自OI-wiki的图,我记得刘汝佳书里也有,不过那本书不在我手边
千灵域
2022/06/17
6990
树状数组、线段树与RMQ
树状数组初探
在前一篇文章:线段树初探 中我们看了一下线段树的基本思想并且知道了线段树擅长于解决区间问题。其实对于某些区间问题,我们不仅可以用线段树解决,还可以用树状数组解决。那么可能有小伙伴要问了,那既然线段树和树状数组都可以解决某些区间问题,那么我就一直用线段树就好了啊,为什么还要学树状数组呢?对于这个问题,我这里能给的答案是:对于两者都能解决的区间问题,两者所用的时间复杂度都是O(logn),树状数组所用的内存空间比线段树更小,还有一个点是:实现树状数组的代码会比线段树的代码更少也更简单。下面来看一下树状数组的基本思想:
指点
2019/01/18
9200
树状数组初探
树状数组
树状数组即二叉索引树,是使用数组模拟树形结构的一种数据结构,可用于计算前缀和和区间和(元素全为1时可用来计数)。采用数组而不是直接建树来解决问题是由于某些特定问题比如区间求和完全可以不建树就能解决,这样实现简单,复杂度低。这点上和Trie树有异曲同工之妙。
Steve Wang
2020/11/03
1.5K0
树状数组
LeetCode 307. 区域和检索 - 数组可修改(树状数组)
给定一个整数数组 nums,求出数组从索引 i 到 j (i ≤ j) 范围内元素的总和,包含 i, j 两点。
Michael阿明
2020/07/13
4300
树状数组的区间修改与查询
我们知道树状数组是支持单点修改和区间查询的,但是如何进行区间修改呢? 直接进行多次单点修改的话,效率是很低的。 对于这个问题,我们可以采用差分的方式去解决 题目: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
8630
树状数组的区间修改与查询
进阶版树状数组
(y*query(tree,y)-(x-1)*query(tree,x-1))-(query(tree1,y)-query(tree1,x-1))
glm233
2020/09/28
3680
算法学习笔记(2)树状数组【转】
树状数组(Binary Index Tree, BIT)也是很多OIer心中最简洁优美的数据结构之一。最简单的树状数组支持两种操作,时间复杂度均为 :
Chuanrui 初见之旅
2022/11/14
4200
算法学习笔记(2)树状数组【转】
京东开发团队带您一起深入理解树状数组
树状数组(BIT, Binary Indexed Tree)是简洁优美的数据结构,它能在很少的代码量下支持单点修改和区间查询,我们先以 a[] {1, 2, 3, 4, 5, 6} 数组为例建立树状数组看一下树状数组的样子:
通信行业搬砖工
2023/10/17
2390
京东开发团队带您一起深入理解树状数组
POJ 2155 Matrix(二维树状数组)
       题意是给一个n*n的全是0的矩阵,然后有T次询问,有两种操作,一是让(x1,y1)到(x2,y2)的值取反(0变1,1变0),第二个是询问(x,y)的值。
Ch_Zaqdt
2019/02/26
1.2K0
Light oj 1080 - Binary Simulation(树状数组区间更新点查询)
有一字符串只包含0和1,然后又m组操作,I L R是将从L到R的字符进行翻转操作0变为1、1变为0,Q x表示询问第x的字符。
xindoo
2021/01/22
3900
树状数组解析
prefixSum(idx):求从数组第一个位置到第idx(含idx)个位置所有数字的和。
Tim在路上
2020/09/10
8550
树状数组求逆序对以及相关例题
求逆序对有两种方法:归并排序和树状数组,但是归并排序求得的逆序对是总共的逆序对数量,有些时候我们需要求得某个数后面的逆序对数量或者某个数前面的逆序对数量。
dejavu1zz
2020/11/12
5950
树状数组求逆序对以及相关例题
线段树、树状数组、归并排序模板
题目链接:https://www.luogu.com.cn/problem/P3374
用户11039529
2024/04/25
990
Preprefix sum 差分+树状数组
Si = S(i-1)  + a[x]变为Si = S(i-1)  +y。 修改值就是y - a[x]
用户2965768
2019/04/18
4240
小码匠的信息学江湖:【模板】树状数组 3 :区间修改,区间查询
小码匠
2023/09/19
2980
小码匠的信息学江湖:【模板】树状数组 3 :区间修改,区间查询
HDU 5618 Jam's problem again(三维偏序,CDQ分治,树状数组,线段树)
Jam's problem again Time Limit: 5000/2500 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others) Total Submission(s): 640    Accepted Submission(s): 210 Problem Description Jam like to solve the problem which on the 3D-axis,given  points  If
ShenduCC
2018/04/27
1.1K0
相关推荐
树状数组-从入门到拓展(转载非原创)
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文