C++数据结构与算法分析——树状数组

树状数组

介绍

我们此前已经了解过前缀和与差分,在数组中,求前缀和的时间复杂度为 O ( n ) O(n) O(n),查询元素的时间复杂度为 O ( 1 ) O(1) O(1)。在前缀和数组中,求前缀和的时间复杂度为 O ( 1 ) O(1) O(1),查询元素的时间复杂度为 O ( n ) O(n) O(n),这两种数组的时间复杂度都是根据其最高的时间复杂度来确定的,即 O ( n ) O(n) O(n),那么有没有可能采取一个折衷的办法让修改和查询操作都不至于那么慢呢?树状数组出现了。

树状数组是一种高级数据结构,它主要支持两种操作:

  1. 快速修改数组中的一个数add(int x,int c) O ( l o g n ) O(logn) O(logn)
  2. 快速求出[l,r]的区间和ask(int x) O ( l o g n ) O(logn) O(logn)

树状数组支持前缀和元素修改操作,且时间复杂度都为 O ( l o g n ) O(logn) O(logn)

先修知识

lowbit操作

原理

8 8 8个数据 a [ 1 ] ∼ a [ 8 ] a[1] \sim a[8] a[1]a[8](下标从1开始)为例,设置一个数组 t [ 8 ] t[8] t[8],其中 t [ i ] t[i] t[i]表示以i结尾的长度为lowbit(i)的区间和,图示如下:
在这里插入图片描述
将它看成一颗树, t [ 8 ] t[8] t[8]为根节点,它的儿子有 t [ 4 ] , t [ 6 ] , t [ 7 ] , a [ 8 ] t[4],t[6],t[7],a[8] t[4],t[6],t[7],a[8], t [ 4 ] t[4] t[4]的儿子有 t [ 2 ] , t [ 3 ] , a [ 4 ] t[2],t[3],a[4] t[2],t[3],a[4],因此要动态维护一个前缀和,比如给 a [ 3 ] + 5 a[3] + 5 a[3]+5,只需要将 t t t中所有与 a [ 3 ] a[3] a[3]有关的数加上 5 5 5即可。那么就出现了一个问题,如何找到 t [ i ] t[i] t[i]的所有父节点呢?

寻找 t [ i ] t[i] t[i]的所有父节点

我们用二进制来找联系:
2 10 = 1 0 2 2_{10} = 10_2 210=102
3 10 = 1 1 2 3_{10} = 11_2 310=112
4 10 = 10 0 2 4_{10} = 100_2 410=1002
4是2、3的父亲,容易发现, 10 0 2 = 1 0 2 + l o w b i t ( 2 ) 100_2 = 10_2 + lowbit(2) 1002=102+lowbit(2), 10 0 2 = 1 1 2 + l o w b i t ( 3 ) 100_2 = 11_2 + lowbit(3) 1002=112+lowbit(3),即:
i个点的父亲是i + lowbit(i)
因此给第x个数+c只要给所有a[x]的父节点都加上c即可。

求前缀和

i = 3 i = 3 i=3为例,要求 i = 3 i = 3 i=3的前缀和需要求 t [ 3 ] + t [ 2 ] t[3] + t[2] t[3]+t[2]
3 10 = 1 1 2 3_{10} = 11_2 310=112
2 10 = 1 0 2 2_{10} = 10_2 210=102
i = = 4 i == 4 i==4为例,要求 i = 4 i = 4 i=4的前缀和需要求 t [ 4 ] t[4] t[4]
4 10 = 10 0 2 4_{10} = 100_2 410=1002

容易看出 1 1 2 = 1 1 2 11_2 = 11_2 112=112 1 0 2 = 1 1 2 − l o w b i t ( 3 ) 10_2 = 11_2 - lowbit(3) 102=112lowbit(3) 0 2 = 1 0 2 − l o w b i t ( 2 ) 0_2 = 10_2 - lowbit(2) 02=102lowbit(2)
10 0 2 = 10 0 2 100 _2 = 100_2 1002=1002 0 2 = 10 0 2 − l o w b i t ( 4 ) 0_2 = 100_2 - lowbit(4) 02=1002lowbit(4)

修改操作代码

void add(int x,int c){ // 维护a[x] + c
	for(int i = x; i <= n; i += lowbit(i)) t[i] += c;
}

前缀和代码

int ask(int x){ // 求第x位的前缀和
	int res = 0;
	for(int i = x; i; i -= lowbit(i)) res += t[i];
	return res;
}

参考资料

本博客图片来源—b站

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

L_Hygen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值