树状数组
介绍
树状数组多用于单点修改和区间查询,是一种代码量小的数据结构,扩展数据结构为线段树。
图示

最下面的八个方块代表原始数据数组 a。上面参差不齐的方块(与最上面的八个方块是同一个数组)代表数组 a 的上级——c 数组,即树状数组结构。
c 数组就是用来储存原始数组 a 某段区间的和的,也就是说,这些区间的信息是已知的,我们的目标就是把查询前缀拆成这些小区间。
例如,从图中可以看出:
c2 管辖的是 a[1…2];
c4 管辖的是 a[1…4];
c6 管辖的是 a[5…6];
c8 管辖的是 a[1…8];
剩下的 c[x] 管辖的都是 a[x] 自己(可以看做 a[x…x] 的长度为 1 的小区间)。
lowbit函数
例如,欲查询 a 数组第1元素到第7元素的和,可以分解为c4+c6+c7,即从 c7 开始依次向左跳跃。
跳跃的节点依据 lowbit 函数:
1 2 3 4 5 6 7 8
| int lowbit(int x) { return x & -x; }
|
lowbit函数可以提取节点序号最低位的1,
观察到每次将节点序号最低位1置0,就是下一跳位置,因此可以不断将查询节点进行lowbit直到下一跳为0。
而查询任意区间和只需将左右序号查询结果相减。
1 2 3 4 5 6 7 8 9 10 11 12 13
| int Query(int pos1,int pos2) { int ret1, ret2 = 0; while (pos2 > 0) { ret2 += c[pos2]; pos2 -= LowBit(pos2); } while (pos1 > 0) { ret1 += c[pos1]; pos1 -= LowBit(pos1); } return ret2-ret1; }
|
而在对线段数组进行更新时,只需按照查询方法在每一跳的树状数组节点上进行更新。
算法示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| class SegmentTree{ private: std::vector<int> c; inline int LowBit(int x){ return x&(-x); } public: SegmentTree(int length) { c.resize(length, 0); }
void Update(int pos1, int pos2, int i) { while (pos1 < c.size()&&pos1 <= pos2) { c[pos1] += i; pos1 += LowBit(pos1); } } int Query(int pos1,int pos2) { int ret1, ret2 = 0; while (pos2 > 0) { ret2 += c[pos2]; pos2 -= LowBit(pos2); } while (pos1 > 0) { ret1 += c[pos1]; pos1 -= LowBit(pos1); } return ret2-ret1; } };
|
算法分析
- 时间复杂度 O(logn)
- 空间复杂度 O(n)
例题
- leetcode 218.天际线问题
- leetcode 307.区域和检索 - 数组可修改