数据结构--树状数组
文章目录
- 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
发现只有L
和R+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∑iA[x]=i∗D[1]+(i−1)∗D[2]+...+1∗D[i]=i∗x=1∑iD[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. 参考文献
- 百度百科:树状数组
- 树状数组入门(简单的原理讲解)
- 树状数组详解
- 树状数组 数据结构详解与模板(可能是最详细的了)
数据结构--树状数组相关推荐
- js 数组 实现 完全树_算法和数据结构 | 树状数组(Binary Indexed Tree)
本文来源于力扣圈子,作者:胡小旭.点击查看原文 力扣leetcode-cn.com 树状数组或二叉索引树(英语:Binary Indexed Tree),又以其发明者命名为 Fenwick 树.其初 ...
- 数据结构——树状数组
我们今天来讲一个应用比较广泛的数据结构--树状数组 它可以在O(nlogn)的复杂度下进行单点修改区间查询,下面我会分成三个模块对树状数组进行详细的解说,分别是树状数组基本操作.树状数组区间修改单点查 ...
- 2017西安交大ACM小学期数据结构 [树状数组 离散化]
Problem E 发布时间: 2017年6月28日 12:53 最后更新: 2017年6月29日 21:35 时间限制: 1000ms 内存限制: 64M 描述 给定一个长度为n的序列a ...
- 2017西安交大ACM小学期数据结构 [树状数组,极大值]
Problem D 发布时间: 2017年6月28日 10:51 最后更新: 2017年6月28日 16:38 时间限制: 1000ms 内存限制: 32M 描述 给定一个长度为n的序列a ...
- 2017西安交大ACM小学期数据结构 [树状数组]
Problem C 发布时间: 2017年6月28日 11:38 最后更新: 2017年6月28日 16:38 时间限制: 1000ms 内存限制: 32M 描述 给定一个长度为n的序列a ...
- 数据结构 —— 树状数组
[概述] 树状数组又称二叉索引树,常用于高效计算数列的前缀和.区间和,其查询.修改的时间复杂度为 log(n),空间复杂度为 O(n) 树状数组通过将线性结构转化成树状结构,从而进行跳跃式扫描. 优点 ...
- Java数据结构-树状数组
什么是树状数组?[面试5.0] 使用数组表示多叉树的结构,和优先队列有点类似,区别在于优先队列只表示二叉树 主要用来: 更新数组元素的数值并且求数组前K个元素的总和或平均值 时间复杂度为O(logN) ...
- “高级”数据结构——树状数组
数据结构是每一个程序员都必须要学的,非常重要! 大佬的博客写的真好,虽然我看不懂..... 链接http://www.cnblogs.com/RabbitHu/p/BIT.html
- 树状数组(单点+区间的所有操作)
转载:https://blog.csdn.net/I_believe_CWJ/article/details/80374326 更简洁方便的数据结构--树状数组(基于线段树的实现) 1.单点更新+区间 ...
最新文章
- centos7升级自带的php5.4版本到php5.6
- 【LeetCode从零单排】No102 Binary Tree Level Order Traversal
- 计算机的主存储器可以分为哪两类,2017年计算机应用基础模拟试题「答案」(2)...
- ie下的透明度,用滤镜filter:alpha
- python爬虫---(2)爬虫基本流程
- Real-time 节点
- 初秋最佳运动蓝牙耳机推荐,100-500这几款防水蓝牙耳机可以试试
- VC++两万字总结Windows系统中的Layered分层窗口技术(附源码)
- 【问】历史库存查询的结存数量和商品进销存报表的结存数量对不上
- mac显示和隐藏文件命令
- Win10跳过开机登录界面
- 物理挖洞之分块 !Cocos Creator !
- CSDN日报20170217——《辞职信:写给我的“藤野先生”》
- 华芯微特SWM260读写W25Q128
- AiDD AI+软件研发数字峰会开启编程新纪元
- 你知道吗?除了迅雷,这几款下载神器也不错!
- 基于Java的雷电游戏设计(含源文件)
- php的位运算,php的位运算详解
- 技术人的2020,有苦涩,有收获,饱满热情迎接2021
- Django 框架学习经验分享