树状数组详解(附图解,模板及经典例题分析)
导言
深藏于算法与数据结构中的思想非常的美妙,尤其是当我们一个一个攻克其中的难点,体会其中蕴含的"哲理"时, A 题的自信力也会有所增加,心情也会格外的舒爽。最近重新接触了树状数组和线段树的题目,决定对其进行一定程度上的系统梳理,并与大家分享,如有不足之处,还请指教,大家共同进步
树状数组
1.基本概念
顾名思义,就是像树一样的数据结构,与 Trie 树类似,其结构类型为完全二叉树,节点排列非常的有规律,故我们直接可以使用数组来模拟,以达到简洁而高效的目的
假设我们有一长为 n 的序列 {a1, a2 , ····,an}, 进行以下操作
(1) 单 点 修 改 \color{Orange}单点修改 单点修改: add(x, v)
表示在 第 x
位置上 加上一个数值 v
(2) 区 间 查 询 \color{Orange}区间查询 区间查询: query(x),x <= n
表示区间 [1, x]
上的和,即 query(x) = a1 + a2 + ··· + ax。那么我们可以得出区间[x, y]
上的区间和即为 query(y) - query(x - 1) (前缀和思想)
对于上述操作在 n
很小的情况下,我们完全可以使用差分与前缀和来操作,复杂度是 O(n)。但是,如果 n
很大的情况下,这样的做法效率就会非常低。此时我们就需要一种高效的数据结构来用空间换取时间,也就是树状数组
2.原理推导及如何建立树状数组
如下图所示,我们有
- 数组A: 传入数据的原数组
- 数组C:建立起来的树状数组
( 1 ) 区 间 查 询 − − O ( l o g n ) \color{Turquoise}(1) 区间查询 - -O(logn) (1)区间查询−−O(logn)
从图中我们可以得出,对于树状数组 C 来说
C [ 1 ] \color{green}C[ 1 ] C[1] = A[ 1 ]
C [ 2 ] \color{green}C[ 2 ] C[2] = A[ 2 ] + C [ 1 ] \color{green}C[ 1 ] C[1]
C [ 3 ] \color{green}C[ 3 ] C[3] = A[ 3 ]
C [ 4 ] \color{green}C[ 4 ] C[4] = A[ 4 ] + C [ 3 ] \color{green} C[ 3 ] C[3] + C [ 2 ] \color{green}C[ 2 ] C[2]
C [ 5 ] \color{green}C[ 5 ] C[5] = A[ 5 ]
C [ 6 ] \color{green}C[ 6 ] C[6] = A[ 6 ] + C [ 5 ] \color{green}C[ 5 ] C[5]
C [ 7 ] \color{green}C[ 7 ] C[7] = A[ 7 ]
C [ 8 ] \color{green}C[ 8 ] C[8] = A[ 8 ] + C [ 7 ] \color{green}C[ 7 ] C[7] + C [ 6 ] \color{green}C[ 6 ] C[6]+ C [ 4 ] \color{green}C[ 4 ] C[4]
~~~~~~ = A[ 1 ] + A[ 2 ] + A[ 3 ] + A[ 4 ] + A[ 5 ] + A[ 6 ] + A[ 7 ] + A[ 8 ]
初步发现,对于每一个 C[ x ],我们有
若 x 为 奇 数 \color{Orange}奇数 奇数,C[ x ] = A[ x ]
若 x 为 偶 数 \color{Orange}偶数 偶数,则不然:
C[ 2 ] = A[ 2 ] + A[ 1 ]
C[ 4 ] = A[ 4 ] + A[ 3 ] + A[ 2 ] + A[ 1 ]
C[ 6 ] = A[ 6 ] + A[ 5 ]
C[ 8 ] = A[ 8 ] + A[ 7 ] + A[ 6 ] + A[ 5 ] + A[ 4 ] + A[ 3 ] + A[ 2 ] + A[ 1 ]
根据数学归纳可以得出,此时 C [ x ] = A [ x − 2 k + 1 ] + A [ x − 2 k + 2 ] + A [ x − 2 k + 3 ] + ⋅ ⋅ ⋅ + A [ x ] C[ x ] = A[x - 2^k + 1] + A[x - 2^k + 2] + A[x - 2^k + 3] +···+ A[x] C[x]=A[x−2k+1]+A[x−2k+2]+A[x−2k+3]+⋅⋅⋅+A[x]
带入发现对于 X 为奇数时,也满足这个规律,所以在树状数组中,
( 核 心 ) \color{Red}(核心) (核心) C[ x ] 表示 区间 【 x − 2 k + 1 , x 】 \color{sKyblue}【x - 2^k + 1, ~x】 【x−2k+1, x】 的和
k 表示 x 的二进制表示中从最低位到最高位连续零的长度,也就是最后一位 1 的位置,我们可以通过一个名为 l o w b i t \color{Maroon}lowbit lowbit 的操作将其求出
int lowbit (int x)
{return x & -x ;//返回 x 的最后一位 1
}
如 x = 1010,则 lowbit(x)= 10
x = 101000,则lowbit(x)= 1000
所以, C[ x ] = 【 x − 2 k + 1 , x 】 【x - 2^k + 1, ~x】 【x−2k+1, x】
= 【 x − l o w b i t ( x ) + 1 , x 】 \color{sKyblue}【x - lowbit(x) + 1, ~x】 【x−lowbit(x)+1, x】的和
有了以上结论,对于树状数组来说,我们如何求出下图区间【1, x】 的区间和呢?
既然 C[ x ] 表示区间【x - lowbit(x) + 1, x】的和,那么【1, x】区间的和就是 C[ x ] + 递归段,即 C[ x ] + C[ x - lowbit(x) ] + ··· + C[ 1 ]
int query(int x)// 求出区间 【1 ,x】的和
{int ans = 0;for(int i = x; i ; i -= lowbit(i))ans += c[i];return ans;
}
( 2 ) 单 点 修 改 − − O ( l o g n ) \color{Turquoise}(2) 单点修改- - O(logn) (2)单点修改−−O(logn)
对于单点修改,也就是 在某个位置 x 加上一个数值 V,如下图所示,假设我们对数组C[ 3 ]位置进行更新时,那么它相应的父亲节点存储的和也需要进行更新,可以发现更新就是一个 “爬树” 的过程。一直把修改信息往上传递,直到到达树的最大高度,也就是树状数组的最大容量 MAXN
对于区间查询,我们可以形象化为一个 “下树” 的过程,那么单点修改可以看作它的 “逆运算”,即往高的区间走,所以我们就可以得到下列单点修改的代码段了:
const int MAXN = 100010;
void add(int x, int v)
{for(int i = x; i < MAXN; i += lowbit(i) )c[i] += v;
}
关于树状数组的正确性证明,大家感兴趣的话可以参考一下这篇博客 树状数组正确性证明
3.代码模板
目前为止,我们尚未提及如何来用原数组 A 来初始化树状数组 C,其实对于树状数组的初始化,我们主要有两种方式
暴 力 构 造 树 状 数 组 C [ x ] \color{Red}暴力\color{White}构造树状数组 C[x] 暴力构造树状数组C[x] : 就是直接对每个位置 x 进行一次单点修改,一共进行 n 次操作,时间复杂度为 O ( n l o g n ) \color{Purple}O(nlogn) O(nlogn)
void init() {for(int i = 1; i <= n; i ++ )add(i, a[i]); }
线 性 构 造 树 状 数 组 C [ x ] \color{Green}线性\color{White}构造树状数组 C[x] 线性构造树状数组C[x]:考虑到树状数组的本质,我们知到一个C[x] 管辖的区域是【x - lowbit(x) + 1 ,x】中所有数的和,所以我们可以先对数组A求一个前缀和,利用前缀和 S[ x ] - S[x - lowbit(x)] 来更新数组C[x],即 C[ x ] = S[x] - S[x - lowbit(x)],这样就能线性构造了,时间复杂度为 O ( n ) \color{Purple}O(n) O(n)
void init() {for(int i = 1; i <= n; i ++ )// 求a的前缀和s[i] = s[i - 1] + a[i];for(int i = 1; i <= n; i ++ )// 用前缀和求出cc[i] = s[i] - s[i - lowbit(i)]; }
故此我们即可得到完整的树状数组的模板
const int MAXN = 100010;
// 注意树状数组最大空间一般开到 1e5 + 10int a[MAXN];// 原数组
int c[MAXN];// 树状数组
int n; // 题目所给的数组 A 数据长度int lowbit(int x)// 返回最后一位 1,即 2^k
{return x & -x;
}
int query(int x)// 查询区间 [1, x] 的和
{int ans = 0;for(int i = x; i ; i -= lowbit(i))ans += c[i];return ans;
}
void add(int x, int v)//修改位置 x 的值
{for(int i = x; i < MAXN; i += lowbit(i))c[i] += v;
}
void init()// 初始化, 这里我选用O(nlogn)的方式
{for(int i = 1; i <= n; i ++ )add(i, a[i]);
}
以上内容尚未完全,随着今后学习的推进,我会继续对其进行补充与完善。另外,大家如果觉得我写的还行的话,还请赠予我一个可爱的赞,你的赞对于我是莫大的支持。
【预告】
- 树状数组的拓展及例题
- 线段树详解
树状数组详解(附图解,模板及经典例题分析)相关推荐
- 【数据结构】树状数组详解(Leetcode.315)
前言 最近做题时遇到一个关于树状数组的题力扣https://leetcode-cn.com/problems/count-of-smaller-numbers-after-self/但是CSDN上仅有 ...
- szu 寒训第二天 树状数组 二维树状数组详解,以及树状数组扩展应用【求逆序对,以及动态第k小数】
树状数组(Binary Index Tree) 树状数组可以解决可以转化为前缀和问题的问题 这是一类用以解决动态前缀和的问题 (有点像线段树简版) 1.对于 a1 + a2 + a3 + - + an ...
- AcWing 241 楼兰图腾(树状数组详解)
树状数组 问题引入 树状数组是一种实现起来比较简单的高级数据结构. 我们知道,对于一个数组a[i],其前缀和s[i]表示a数组里面前i个元素之和,而求区间l到r的元素之和可以用s[r] - s[l-1 ...
- 树状数组详解(超详细)(完整代码在四 五最后)
一,树状数组的优点 前缀和的思想,可以通过O(n)的预处理,使得多次查询区间值都是o(1),但只能解决不修改,多次查询的问题. 差分思想,能通过差分数组,将区间修改变成O(1)的,最后通过一次O(n) ...
- Win+TexLive2020+TexStudio安装过程详解附ElsevierLatex模板下载并使用
Win+TexLive2020+TexStudio安装过程详解附ElsevierLatex模板下载并使用 一.下载并安装Texlive2020 1.下载TexLive2020 2.安装过程 解压之后运 ...
- Tre树(字典树)数据结构详解(图解)及模板
了解这个数据结构之前我们需要了解它能被用来做什么 字典树又称单词查找树,Tire树,是一种树形结构,是一种哈希树的变种.典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引 ...
- 递归算法详解——递归算法的三要素以及例题分析
目录 1递归的三要素 1.1明确函数的功能 1.2递归的结束条件 1.3函数的等价关系 2递归案例 递归算法(英语:recursion algorithm)是指一种通过重复将问题分解为同类的子问题而解 ...
- 数据结构--树状数组
文章目录 1. 树状数组 2. 单点修改 3. 区间修改 4. 完整代码 5. 参考文献 1. 树状数组 类似数据结构:线段树(Segment Tree) 树状数组 跟 线段树 的区别: 树状数组能做 ...
- 线。段。树--树状数组-主席树
简单了解一下线段树 以前写过的内容,搬运过来 线段树的应用场景:满足区间加法性质且多次查询,什么是区间加法性质,比如最大值,求和,树状数组.线段树.主席树依次. 线段树框架:建树--查询--更新... ...
最新文章
- AngularJS 表单数据验证及错误信息提示
- 用php实现Google /Baidu Ping服务快速收录
- 移动端页面按手机屏幕分辨率自动缩放的js
- java如何实现e的次方_java 大数据次方运算
- 阿里、美团、滴滴产品经理共述:产品经理的“乐”与“伤”
- 自定义日历控android,android 一个简单的自定义日历控件,让你掌控时间
- 从Slice_Header学习H.264(三.3)--相关细节之 FMO
- linux HUSTOJ 一些页面修改
- 使用人工智能加快海底数据处理-从粗略过滤到精细智能数据筛选
- 哈工大计算机系统2022大作业:程序人生-Hello‘s P2P
- Gimp 将图片中的颜色更改
- Gamit10.6基线解算和网平差
- 新手购买服务器搭建属于自己的网站(详细版)
- vscode一键生成佛祖保佑永无bug
- 爬取软科中国最好大学排名
- c语言开机自启动 linux_Linux开机启动程序rc.local
- Quartus II 操作入门
- Python(x,y)安装
- 光电二极管的采样电路
- 哈工大计算机网络研究生,2020年哈尔滨工业大学软件工程考研经验分享
热门文章
- 【转载】让盐和味精告诉你,「变量」是什么 | 亲子课堂 第 4 课
- 人机的根本区别:Free will
- GPS设置及原理教程-如何通过修改gps.conf文件来提高搜星速度
- 海康威视:笔试题(20190908)
- 全国首个数字产权区块链平台上线,共享购模式悄然上市
- 浅谈领域驱动设计(DDD:Domain-Driven Design)
- 视频号领域发布的不同时间,视频号上热门秘诀:国仁楠哥
- Android 隐藏/透明小白条
- 工具类批量修改照片的名字
- 腾讯云轻量服务器Centos 7.6安装redis 5.0.4教程