一、概述
树状数组即Binary Indexed Tree或者Fenwick Tree,是一种用于高效处理数字的列表更新和求范围和的数据结构。它可以以O(logn)的时间得到前缀和num[1..i],并同时支持O(logn)时间内支持动态单点值的修改。
树状数组解决的问题就是存在一个长度为n的数组nums,我们如何高效的执行以下操作:
- update(ind,val) 将val的值加到数组的ind位置上
- prefixSum(ind) 求数组前ind个位置上的数字和
- rangeSum(from,to) 求数组[from..to]求数和
当执行update的数量很少,prefixSum或者rangeSum的操作很多时,我们可以为原数组nums创建一个相同大小的数组sum,sum[i]表示前i个(包括第i个)的数字和。这样prefixSum(ind)=sum[ind],rangeSum(from,to) = sum[to]-sum[from]+nums[from].
当update的操作频率增大时,每次update(ind,val)将对数组sum的i>=ind的所有位置进行修改。树状数组就是对这种情况的一种改进。
二、建立BIT
这里我们假设一段大小为15的数组[1,4,3,2,8,7,6,5,4,10,12,14,15,9,13,11],构建后的数组是怎么样的的,用下图表示:
从图中可以看出BIT是用数组表示的并且构造BIT数组的方式是一种分层的方式,具体的做法是每一层的一段连续的位置,假设其中k表示该层某段连续位置的第一个数,则bit[k] = nums[k],当i在这段连续的位置时bit[i]=num[k]+num[k+1]+...+num[i],(i-k为2的指数次方,当i-k!=2的幂次方是bit[i]为空,将其放入下一层计算)。
在正式建立树状数组之前我们介绍一下prefixSum(ind) 和 update(ind,val)这个操作
2.1、prefixSum(ind)
我们已经得到了BIT数组,怎么求原数组中前i个数的和,假设求sum[13].
将13进行分层:13(1101B)=1+4+8
prefixSum(13) = bit[13]+bit[13-1]+bit[13-1-4],从图中可以证明这一点
bit[13] = nums[13] bit[12] = nums[9..12] bit[8] = [1..8];即:bit[1101B]+bit[1100B]+bit[1000B] (每次将二进制的最后一个1变为0将得到下一个bit的位置)。代码表示如下:
public int prefixSum(int[] bit, int idx) { int sum = 0; for(int i=idx;i>=0;i-=i&(-i)) sum+=bit[i]; return sum; }
其中i-=i&(-i)
即为减去二进制形式最右边的1得到的值。
2.2、 update
ind是在原数组上要修改的位置,val表示在ind位置上增加的值(如果val是修改后的值,可以将其与原值相减得到增加的值)。假设我要在nums[5]的位置上增加val,那么bit相应改变的位置有bit[5],bit[6],bit[8]。5->101B,6->110B,8->1000B,观察BIT数组下标的二进制形式,每次都为在最右边为1的位置上加上1。
update的代码如下:
public void update(int[] bit,int idx,int val) { for(int i=idx;i<bit.length;i+=i&(-i)) bit[i]+=val; }
23、创建BIT数组
创建BIT数组就是首先初始化一个与原数组nums相同大小的数组bit全为0,遍历nums当下标为i时执行操作update(bit,i,nums[i]);
for(int i=1;i<nums.length;i++) update(bit,i,nums[i]);
注意本文中所有数组的下标都是从1开始。