文章目录

  • 1. 树状数组
  • 2. 单点修改
  • 3. 区间修改
  • 4. 完整代码
  • 5. 参考文献

1. 树状数组

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

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

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

树状数组的 查询 和 修改 复杂度都为 log⁡(n)\log(n)log(n)

  • 原数组为 A
  • 树状数组为 C(注意下标从1开始!!!
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构成

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

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
#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位置加上deltawhile(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;
}

结果:

6
36
8
38
9

3. 区间修改

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

A[i] = 1 2 3 5 6 9
D[i] = 1 1 1 2 1 3 (差值)

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

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[i−1]+...+D[1]A[i−1]=D[i−1]+D[i−2]+...+D[1]...A[2]=D[2]+D[1]A[1]=D[1]A[i] = D[i] + D[i-1]+...+D[1]\\ A[i-1] = D[i-1] + D[i-2]+...+D[1]\\ ... \\ A[2] = D[2] +D[1]\\ A[1] = D[1]A[i]=D[i]+D[i−1]+...+D[1]A[i−1]=D[i−1]+D[i−2]+...+D[1]...A[2]=D[2]+D[1]A[1]=D[1]
左右都加起来:
∑x=1iA[x]=i∗D[1]+(i−1)∗D[2]+...+1∗D[i]=i∗∑x=1iD[x]−∑x=1i(D[x]∗(x−1))\sum\limits_{x=1}^i A[x] = i*D[1] + (i-1)*D[2] +... +1*D[i] =i*\sum\limits_{x = 1}^i D[x] - \sum\limits_{x = 1}^i ( D[x]*(x-1))x=1∑i​A[x]=i∗D[1]+(i−1)∗D[2]+...+1∗D[i]=i∗x=1∑i​D[x]−x=1∑i​(D[x]∗(x−1))
看见最后两项,就明白了吧,再增加一个树状数组 存入 D[x]∗(x−1)D[x]*(x-1)D[x]∗(x−1)

#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位置加上deltaint 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】,都加上2cout << query_p(3) << endl;cout << query_p(8) << endl;cout << query_range(3,4) << endl;//获取A[3],A[4]的区间和return 0;
}

运行结果

6
36
8
38
9
  • 相关题目:
    程序员面试金典 - 面试题 10.10. 数字流的秩(map/树状数组)
    LeetCode 307. 区域和检索 - 数组可修改(树状数组)

4. 完整代码

/*** @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位置加上deltaint 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】,都加上2cout << query_p(3) << endl;cout << query_p(8) << endl;cout << query_range(3,4) << endl;//获取A[3],A[4]的区间和return 0;
}

5. 参考文献

  • 百度百科:树状数组
  • 树状数组入门(简单的原理讲解)
  • 树状数组详解
  • 树状数组 数据结构详解与模板(可能是最详细的了)

数据结构--树状数组相关推荐

  1. js 数组 实现 完全树_算法和数据结构 | 树状数组(Binary Indexed Tree)

    本文来源于力扣圈子,作者:胡小旭.点击查看原文 力扣​leetcode-cn.com 树状数组或二叉索引树(英语:Binary Indexed Tree),又以其发明者命名为 Fenwick 树.其初 ...

  2. 数据结构——树状数组

    我们今天来讲一个应用比较广泛的数据结构--树状数组 它可以在O(nlogn)的复杂度下进行单点修改区间查询,下面我会分成三个模块对树状数组进行详细的解说,分别是树状数组基本操作.树状数组区间修改单点查 ...

  3. 2017西安交大ACM小学期数据结构 [树状数组 离散化]

    Problem E 发布时间: 2017年6月28日 12:53   最后更新: 2017年6月29日 21:35   时间限制: 1000ms   内存限制: 64M 描述 给定一个长度为n的序列a ...

  4. 2017西安交大ACM小学期数据结构 [树状数组,极大值]

    Problem D 发布时间: 2017年6月28日 10:51   最后更新: 2017年6月28日 16:38   时间限制: 1000ms   内存限制: 32M 描述 给定一个长度为n的序列a ...

  5. 2017西安交大ACM小学期数据结构 [树状数组]

    Problem C 发布时间: 2017年6月28日 11:38   最后更新: 2017年6月28日 16:38   时间限制: 1000ms   内存限制: 32M 描述 给定一个长度为n的序列a ...

  6. 数据结构 —— 树状数组

    [概述] 树状数组又称二叉索引树,常用于高效计算数列的前缀和.区间和,其查询.修改的时间复杂度为 log(n),空间复杂度为 O(n) 树状数组通过将线性结构转化成树状结构,从而进行跳跃式扫描. 优点 ...

  7. Java数据结构-树状数组

    什么是树状数组?[面试5.0] 使用数组表示多叉树的结构,和优先队列有点类似,区别在于优先队列只表示二叉树 主要用来: 更新数组元素的数值并且求数组前K个元素的总和或平均值 时间复杂度为O(logN) ...

  8. “高级”数据结构——树状数组

    数据结构是每一个程序员都必须要学的,非常重要! 大佬的博客写的真好,虽然我看不懂..... 链接http://www.cnblogs.com/RabbitHu/p/BIT.html

  9. 树状数组(单点+区间的所有操作)

    转载:https://blog.csdn.net/I_believe_CWJ/article/details/80374326 更简洁方便的数据结构--树状数组(基于线段树的实现) 1.单点更新+区间 ...

最新文章

  1. centos7升级自带的php5.4版本到php5.6
  2. 【LeetCode从零单排】No102 Binary Tree Level Order Traversal
  3. 计算机的主存储器可以分为哪两类,2017年计算机应用基础模拟试题「答案」(2)...
  4. ie下的透明度,用滤镜filter:alpha
  5. python爬虫---(2)爬虫基本流程
  6. Real-time 节点
  7. 初秋最佳运动蓝牙耳机推荐,100-500这几款防水蓝牙耳机可以试试
  8. VC++两万字总结Windows系统中的Layered分层窗口技术(附源码)
  9. 【问】历史库存查询的结存数量和商品进销存报表的结存数量对不上
  10. mac显示和隐藏文件命令
  11. Win10跳过开机登录界面
  12. 物理挖洞之分块 !Cocos Creator !
  13. CSDN日报20170217——《辞职信:写给我的“藤野先生”》
  14. 华芯微特SWM260读写W25Q128
  15. AiDD AI+软件研发数字峰会开启编程新纪元
  16. 你知道吗?除了迅雷,这几款下载神器也不错!
  17. 基于Java的雷电游戏设计(含源文件)
  18. php的位运算,php的位运算详解
  19. 技术人的2020,有苦涩,有收获,饱满热情迎接2021
  20. Django 框架学习经验分享

热门文章

  1. ios微信本地视频上传到服务器,ios本地视频wx.uploadFile上传
  2. java ajax 定时刷新_用ajax技术实现无闪烁定时刷新页面
  3. git 使用及常用命令介绍
  4. 关于Treap的学习感受
  5. 简单的FreeBSD 的内核编译
  6. Runtime消息动态解析与转发流程
  7. idea,eclipse创建多模块项目
  8. MySql5.7.12设置log-bin
  9. *如何循序渐进向DotNet架构师发展(转)
  10. mysql开启慢查询日志及查询--windows