zoukankan      html  css  js  c++  java
  • 树状树组(Binary Indexed Tree (BIT))的C++部分实现

    一、树状数组的用处

    树状树组是将一个线性数组保存为“树状”,当修改某点的值、求某个区间的和的时候能够有效的减少时间复杂度。当数组长度为N,实时对数组进行M次修改或求和,最坏的情况下复杂度是O(M*N)。

    二、树状数组的建立

    假设输入数组为

    vector<int> nums
    

    将其转化为树状数组的本质在于将数组的原先顺序打乱后,经过特殊的求和方法,组合成新的数组,代码如下。关键点在于k+=k&-k,这是一个利用二进制码的特点完成树状数组下标的选取。

     1 size = nums.size();
     2 bitTree = vector<int>(size+1,0);
     3 for (int i =1;i<=size;i++)
     4 {
     5        int k=i;
     6        while (k<size+1)
     7        {
     8               bitTree[k] += nums[i-1];
     9               k += k & -k;
    10         }
    11  }

    树状数组建立的原理:

    如下图,来源自百度图片,c数组是树状数组,a数组是原先的线性数组,可以看见树状数组的元素是a数组的元素或者元素之和,其中c[8]+c[9]即是线性数组的a的总和。对于树状数组而言,其建立顺序(按下标排列)是(循环1)c[1]+=a[0];c[2]+=a[0];c[4]+=a[0];c[8]+=a[0];

                      (循环2)c[2]+=a[1];c[4]+=a[1];c[8]+=a[1];

                      (循环3)c[3]+=a[2];c[4]+=a[2];c[8]+=a[2];

                      (循环4)c[4]+=a[3];c[8]+=a[3];

                      (循环5)c[5]+=a[4];c[8]+=a[4];

                      (循环6)c[6]+=a[5];c[8]+=a[5];

                      (循环7)c[7]+=a[6];c[8]+=a[6];

                      (循环8)c[8]+=a[7];

                      (循环9)c[9]+=a[8];

    可见,第一次循环加的是1,2,4,8;第二次是2,4,8;第三次是3,4,8等等。其规律是第i循环中,c[k]与a[i-1]相加,而k与i的关系是k是i二进制数保留最高位1后相加的结果,比如i=2=0010,其二进制数保留最高位1后是0010,故第1个k=0010+0010=0100=4,再对k进行处理,得第二个k=8,直至k>size+1。又比如i=3=0010,其二进制数保留最高位1后是0010,第1个k=0010+0010=0100,第2个k=0100+0100=8。之所以采用这种方法因为树状数组采用了二分的思想,比如c[8]会等于a[0]~a[3]与a[4]~a[7]两部分的和。

    故建立数组的关键在于求i二进制数保留最高位1后相加的结果,其方法是:令k=i,k+=k&-k,即可求得结果。

    三、树状数组的更新和部分求和

    更新数组:如果此时原数组中的一个元素被改变,那么树状数组中许多值需要被更新,这因为树状数组中的元素之间存在可能的联系,这种联系与树状数组下标值相关。因此更新树状数组并不是单纯的单个值替换,代码如下:

     1     void update(int i, int val) {
     2         int ret = val-nums[i];
     3         int k=i+1;
     4         while (k<size+1)
     5         {
     6             bitTree[k]+=ret;
     7             k+=k&-k;
     8         }
     9         nums[i] = val;
    10     }

    数组部分求和:分别先求0到i下标的和,再求0到j+1下标的和,它们之间的差即是下标i到下标j的和,数组部分求和代码如下:

     1     int sumRange(int i, int j) {
     2         int result1 = 0,result2 = 0;
     3         int k=i;
     4         while (k)
     5         {
     6             result1+=sum[k];
     7             k-=k&-k;
     8         }
     9         k=j+1;
    10         while (k)
    11         {
    12             result2+=sum[k];
    13             k -= k&-k;
    14         }
    15         return result2-result1;
    16     }
  • 相关阅读:
    Android自动化测试解决方案
    Oracle数据库的DML命令的处理过程详解
    Oracle数据库的BULK COLLECT用法之批量增删改
    建设DevOps能力,实现业务敏捷
    强大的C# Expression在一个函数求导问题中的简单运用
    Visual Studio 11开发者预览版发布(附下载)
    js table隔行变色
    编译原理语法推导树
    巧用数据库归档技术解决性能下降问题
    编译原理正规式和有限自动机
  • 原文地址:https://www.cnblogs.com/ycloneal/p/5191256.html
Copyright © 2011-2022 走看看