数据结构 - 树状数组 Binary Indexed Tree
在算法竞赛和实际应用中,处理动态数据时经常遇到以下问题:
- 区间求和:快速求解某个区间的元素总和;
- 单点更新:快速更新某个元素的值,同时保证区间查询结果的正确性。
如果使用简单的数组实现,区间查询的复杂度为 ,单点更新的复杂度为 。而如果选用前缀和数组,查询变成了 ,但更新需要 。如何同时兼顾高效的查询与更新呢?树状数组,也成为二叉索引树(Binary Indexed Tree)提供了优雅的解决方案,其查询与更新的时间复杂度均为 。本文将深入讲解树状数组的原理、实现与应用。
1. 什么是树状数组
树状数组的核心是通过数组下标的二进制特性,将数据划分为不同的区间。每个数组元素存储一个子区间的累加值,利用这些部分和快速计算前缀和或更新元素。其特性如下:
- 树状数组的本质是一棵隐式的二叉索引树,存储在一个一维数组中;
- 下标 的元素存储的是区间 的和,其中 是 的二进制表示中最低位 1 的位置。例如:
- (二进制为 ),维护区间 ;
- (二进制为 ),维护区间 。
2. 树状数组的基本操作
2.1 单点更新
更新某个下标元素值时,需要修改受影响的所有区间值。更新规则是:
其中, 表示 的二进制中最低位 1 所代表的值,可以通过位运算求得:
int lowbit(int x) {
return x & (-x);
}
void update(int index, int delta) {
while (index <= tree.size()) {
tree[index] += delta;
idx += lowbit(index);
}
}
2.2 前缀和查询
查询数组前 个元素的前缀和时,只需累加相关区间的值即可。查询规则为:
int query(int index) {
int sum = 0;
while (index > 0) {
sum += tree[index];
index -= lowbit(index);
}
return sum;
}
如果需要查询的是区间 内元素的和,那么可以转化为对区间 和区间 前缀和的查询再做差分。
3. 树状数组的复杂度分析
- 时间复杂度:修改和查询操作的时间复杂度都是 。
- 空间复杂度:。
4. 树状数组的模板
class BinaryIndexedTree {
public:
BinaryIndexedTree(int n) {
tree.resize(n + 1, 0);
}
void update(int index, int delta) {
while (index < tree.size()) {
tree[index] += delta;
index += lowbit(index);
}
}
int query(int index) {
int sum = 0;
while (index > 0) {
sum += tree[index];
index -= lowbit(index);
}
return sum;
}
int rangeQuery(int left, int right) {
return query(right) - query(left - 1);
}
private:
std::vector<int> tree;
int lowbit(int x) {
return x & (-x);
}
};
5. 实例
本文作者:cnblogs_wb
本文链接:https://www.cnblogs.com/wenbinteng/p/18734101
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
分类:
数据结构与算法
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现