leetcode刷题笔记-树状数组

树状数组

前言

树状数组是个比较冷门的算法,主要用来解决区间和问题。树状数组能解决的问题,使用线段树都能解决;但线段树能解决的问题,并不是使用树状数组都可以解决,建议大家了解即可选择性学习。
一、了解树状数组

如现在有问题是:给一个数组,求下标为第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/

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值