区间问题1——从零开始引出树状数组

首先要明确的是,树状数组和线段树都是用来解决规模相对较大的区间问题的,区间问题就是,给一串数据,他们都属于一个范围内,我们需要从这个大的范围内找到一个更小的范围,去查询,修改或者计算给出的数据在这个小范围内的相关信息(最大最小值,和等等)。

例如:给出一个数列,让你求出该数列中第x项到第y项(x<y)之间的最小值,给某个数加上a,或者求x项到y项的和。简单的思路有两个:

  1. 最暴力的方法自然是全部存入一个数组,然后根据下标来做到需要做的事:
    假设数列有n个数,
    如果是需要修改某个值,那只需要找到对应的下标进行修改就行了,一次修改的时间复杂度为O(1);
    如果要找区间最大最小值或者求和,假设区间大小是m,每次操作都要遍历m次,m最大能取到n,那时间就是O(n);

  2. 那么可不可以用前缀和的方式来记录数列呢?假设数列是1,3,4,6,我记录的时候变成1,1+3,1+3+4,1+3+4+6,即依据数组前一个存下的值加上当前的值来存入数组,即1,4,8,14,这样求区间和只要减一减就出来了,而且数列的每一个信息只要减去前一位就可以了。例如求第一个和第四个数之间所有数的和,只要把第四个值14减去第二个值1,就能得到13(3+4+6),想要知道第三个数是几,只要8-4=4即可。

这样的好处很显然,找区间和变成了一次操作就能完成的事情,时间复杂度变为了O(1);

但是当我们去更新这个数组的时候,改动一个前缀和,就得把之后所有的前缀和都改动,比方说把1,3,4,6改成1,2,4,6,那么从第二个起的值因为是前缀和,每一个都得减去1,那么修改一次最大会需要O(n);

同样,找最大最小值的时候,因为单个值还是得去减前一个值得到,和普通数组无异,操作最大需要遍历n次,时间也是O(n);

这样看来两种思路都有优劣,但是通常情况下我们只会选择一种方式存数据,但同时需要做到更新和调用信息,这两种方法单次操作最大时间是O(n),那么n个数最多n次操作时,总时间复杂度会变成O(n^2);

于是需要一种更好的存储数据的方式,同时要求更新和查询的时间复杂度都低于O(n^2),树状数组和线段树就是这么随需求诞生的。

首先前缀和在查找区间和的时候的优势是我们想要的,同时直接存数组时更新快的优势也是我们想要的,那能不能结合一下呢?树状数组就用了一种很巧妙的方式去构建了一个可以同时做到这两个优势的数组。

树状数组

思路引出

首先分析一下前缀和为什么能更快的查找区间和,是因为它每次存储的时候,把前面的信息也包括在内了,第二项包括了1和2的值,第三项包括了1、2和3的值,而直接存数组,因为每个值的信息是独立的,所以更新时不需要考虑前面的信息的变动。这两者是矛盾的,因为想快一些得到和的信息就需要在数组的每一项里存更多的信息以便于减少求和的操作次数,而想快一点更新数据则需要数组里的每一项信息更加独立,避免更新的时候需要同步更新。

显然前两种办法是对这两个矛盾的取舍各取了一个极端。于是我们的目的就变得明确了,要有一个折中的思路,不能一股脑保留所有的信息去前缀和,要让他们信息本身的独立性更强以便更新,同时又希望他们能尽量的多保留一些前面的信息,我们要在这两个选择中找一个平衡。

那么一个思路就是增加前缀和的区间分段,本身只用前缀和的时候,我是把前面所有的信息全部存下作为和存进数组,那我是否可以隔一段距离进行存储,也就是把大区间分段,每一次存储只取一段的信息求和存入。也就是说,我保留一些区间内计算的结果,节省求和时间,但尽量减少区间信息的叠加,从而减少更新时间。可以参考下图取法(接下来假设的取法有缺陷,只是思路引导)
在这里插入图片描述
假设按图中这样,把大区间分段,然后对每个段求和,那么首先,8,13,30这几个红色的数包含了一定量的原始1-9项的信息,而且各自只受自己下方区间的影响,比如更新第二项的6为7时,只需要更改8为9,后面的三项红色的数字不需要改变就能像一开始一样的表达出修改前同样的信息量(再强调一次,信息量有欠缺)。

这样虽然一定程度提高了找区间和的办法,但是只是特定的区间比较好找了,比方说找3-5项,那么13就能胜任需要的信息量,但是如果我想找1-9,还是得遍历8,13,30,然后相加得到51,也就是说一旦要找更大区间的时候,还是需要遍历一定量的时间,所以我们干脆再往上套一层。用一个更高的维度来收集更多的区间信息,也就是用套娃的办法把我们取出的分段再当成分散的数据分段,来减少区间分段的个数,如图所示:
在这里插入图片描述
这种套娃避免了我们想找更大范围的和时仍需要遍历多个区间的过程,同时因为依旧是分段求和,所以在更新的时候只是多了一层的更新,比如更新第4项8为9时,我只要改动13,51,76,而不需要动其他的值,相比于整个前缀和存入的取法依旧是更独立的,因为仍有一大部分的数据我们不需要遍历到。

但是我相信有个很明显的问题可以看出来,就是这种不规律的区间取法会丢失了一些中间值的信息,比如我想找5和6项的和时,我没法利用13和30去得出1+3=4,因为我存储的时候是按分段和存的,单个的信息没法保证(也就是前面提到的,虽然信息有了独立性,但是独立性不够充分导致有些信息损失了)

显然,这种套娃式分段确实迈出了一小步,因为如果不考虑这种信息的损失,它确实加速了我们对于某些区间的求和以及更新的速度,对于特定的区间,进行求和和更新的单次操作都会低于O(n)。这个信息的损失,显然是我们在分段的时候没有做好。

那么怎么分段才能避免信息的丢失,又能保留这种套娃式的高效呢?

树状数组的原理

一个重要原理:任何正整数都能表示为2的幂相加的形式,表示成二进制就是一位一位的相加,比如11=8+0+2+1,二进制就是11=1010,意为23+0+21+2^0,这也是树状数组采取的分段的方式,每套一层娃就是二进制的一位,而每一层下面都有更低一位的二进制保证取到所有值,例如
在这里插入图片描述
1-4小于22,而5-8可以用22+a 的形式表示,而a又可以以21+b 来表示,直到20次为止,所以一个数可以依据2的幂次求和得到。也就是,按照下标排,每逢2的幂次,就会套一层分段,如果对应到数列例子中 ,具体看下图:

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值