树状数组
前言
树状数组是个比较冷门的算法,主要用来解决区间和问题。树状数组能解决的问题,使用线段树都能解决;但线段树能解决的问题,并不是使用树状数组都可以解决,建议大家了解即可选择性学习。
一、了解树状数组
如现在有问题是:给一个数组,求下标为第n1个数到第n2个数的区间和。为了方便说明和理解,我们假设这个数组拥有8个元素,即vector v = {3,4,5,1,4,5,6,7},计算下标第3个数到第7个数的区间和。常规方法为遍历下标3-7的元素,相加即可。现在我们使用树状数组来解决。如下图:
先来解释图片的意思,上面的树状图即为树状数组,下面的A[1] - A[8]即为原数组。树状数组使用与原数组相等大小,来代表一个树状结构,树状数组C对应原数组A有如下对应关系。
C[1] 代表 A[1]的值
C[2] 代表 A[1] + A[2]的值
C[3] 代表 A[3]的值
C[4] 代表 A[1] + A[2] + A[3] + A[4]的值
C[5] 代表 A[5]的值
C[6] 代表 A[5] + A[6]的值
C[7] 代表 A[7]的值
C[8] 代表 A[1] +…+ A[8]的值
大家先不必在意,树状数组是以什么规则创建出来的,但是不难看出通过树状数组C,可以求出原数组A任意区间的和。
二、如何求出树状数组?
1、树状数组的基本操作
1)lowbit
说明:
lowbit是树状数组的核心操作,即取数字的最低位。起初我们的原数组A中的元素都为0,那自然树状数组C的所有元素也为0,因为任意区间和都是0.现在我们将第三个元素的值+1,我们看上面的树状数组图那个树状数组C将有哪些元素的值会受到影响+1?即为C[3]、C[4]、C[8],因为它们各自代表的区间和均包含了A[2]元素。
而下标3、4、8的二进制数字分别为011、100、1000。这三个数字的关系就需要使用lowbit来关联起来。我们现在知道lowbit是得到当前数字的最低位,因此对3(011)进行lowbit运算得到的是1(001),而3(011)+1(001)恰好是4(011+001=100)。
我们再对4做lowbit运算,因为4(100)的最高位是1而后面都是0,通过lowbit运算得到最低位还是4(100),而4(100)+4(100)恰好就是8(1000),8已经达到树状数组大小的上限。因此停止计算。
方法:
在此我直接给出lowbit的代码,因为其它文章对该函数的代码原理解释很多,在这里不做其它说明。
int lowbit(int x)
{
return x & (-x);
}
2)update单点更新操作
前面在介绍lowbit时A[3]的值加1,更新树状数组C中下标3、4、8位置元素的步骤,就是update单点更新操作。即原数组值的变化对树状数组的影响。
在此直接给出代码,代码比较简单。
void update(int x, int d) //X为原数组值变更的下标、d为变更的值
{
while (x <= n) //n为树状数组大小
{
tree[x] += d;//tree为树状数组
x += lowbit(x);
}
}
3)query查询操作
假如针对上面的问题,现在要求前7个元素的合。使用树状数组需要如何求解?看树状数组图可知,前7个元素的和就等于C[7] + C[6] + C[4],同样我们观察3个下标的二进制数字,7(111) 、6(110)、4(100),3个数字结合上面的lowbit最低位操作有什么关系?
不难看出
7-lowbit(7) = 6,
6-lowbit(6) = 4,
4 - lowbit(4) = 0,
到0停止
可得出query查询操作,就是一个对下标不断-lowbit的循环,直至下标<=1时中断循环,这些元素相加即为前7个元素的和。
int query(int x) const //x为下标,查询第1个元素至第x元素的区间和
{
int ans = 0;
while (x)
{
ans += tree[x];//tree为树状数组
x -= lowbit(x);
}
return ans;
}
三、树状数组例题
求前7个元素区间和的完整代码如下:
class BIT {
private:
vector<int> tree;
int n;
public:
BIT(int _n) : n(_n), tree(_n + 1) {}
static constexpr int lowbit(int x) {
return x & (-x);
}
void update(int x, int d) {
while (x <= n) {
tree[x] += d;
x += lowbit(x);
}
}
int query(int x) const {
int ans = 0;
while (x) {
ans += tree[x];
x -= lowbit(x);
}
return ans;
}
};
void main()
{
BIT bm(8);
vector<int> v = { 3,4,5,1,4,5,6,7 };
int i = 1;
for (auto num : v)
{
bm.update(i, num);
i++;
}
int x = bm.query(7);
}
更多进阶题可在leetcode查找:
#493 翻转对:https://leetcode-cn.com/problems/reverse-pairs/
#327 区间和的个数:https://leetcode-cn.com/problems/count-of-range-sum/
#315 计算右侧小于当前元素的个数:https://leetcode-cn.com/problems/count-of-range-sum/