树状数组(Binary Indexed Tree)

社会主义新天地 提交于 2019-12-02 12:32:43

一、概述

  树状数组即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开始。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!