树状数组(转载)

摘要:
树数组是一种用于更改元素和对数组求和的实用数据结构。因此,这里我们介绍“树阵列”。它的修改和求和是O,这是非常有效的。为了获得树阵列的图像,让我们先看下图。如图所示,红色矩形表示的数组C[]是一个树数组。到目前为止,我们已经详细分析了树阵列的复杂性和原理。最后,我们将给出一些树数组的实现代码,希望读者能够仔细理解其中的细节。

树状数组是对一个数组改变某个元素和求和比较实用的数据结构。两中操作都是O(logn)。 
 在解题过程中,我们有时需要维护一个数组的前缀和S[i]=A[1]+A[2]+...+A[i]。

          但是不难发现,如果我们修改了任意一个A[i],S[i]、S[i+1]...S[n]都会发生变化。

          可以说,每次修改A[i]后,调整前缀和S[]在最坏情况下会需要O(n)的时间。

          当n非常大时,程序会运行得非常缓慢。

          因此,这里我们引入“树状数组”,它的修改与求和都是O(logn)的,效率非常高。

【理论】

          为了对树状数组有个形 象的认识,我们先看下面这张图。

树状数组(转载)第1张

          如图所示,红色矩形表示的数组C[]就是树状数组。

          这里,C[i]表示A[i-2^k+1]到A[i]的和,而k则是i在二进制时末尾0的个数,

          或者说是i用2的幂方和表示时的最小指数。

         ( 当然,利用位运算,我们可以直接计算出2^k=i&(i^(i-1)) )

          同时,我们也不难发现,这个k就是该节点在树中的高度,因而这个树的高度不会超过logn。

          所以,当我们修改A[i]的值时,可以从C[i]往根节点一路上溯,调整这条路上的所有C[]即可,

          这个操作的复杂度在最坏情况下就是树的高度即O(logn)。  

          另外,对于求数列的前n项和,只需找到n以前的所有最大子树,把其根节点的C加起来即可。

          不难发现,这些子树的数目是n在二进制时1的个数,或者说是把n展开成2的幂方和时的项数,

          因此,求和操作的复杂度也是O(logn)。

          接着,我们考察这两种操作下标变化的规律:

          首先看修改操作:

          已知下标i,求其父节点的下标。
          我们可以考虑对树从逻辑上转化:

树状数组(转载)第2张
         如图,我们将子树向右对称翻折,虚拟出一些空白结点(图中白色),将原树转化成完全二叉树。

         有图可知,对于节点i,其父节点的下标与翻折出的空白节点下标相同。

         因而父节点下标 p=i+2^k  (2^k是i用2的幂方和展开式中的最小幂,即i为根节点子树的规模)

         即  p = i + i&(i^(i-1)) 。

         接着对于求和操作:

         因为每棵子树覆盖的范围都是2的幂,所以我们要求子树i的前一棵树,只需让i减去2的最小幂即可。

         即  p = i - i&(i^(i-1)) 。

        

         至此,我们已经比较详细的分析了树状数组的复杂度和原理。

         在最后,我们将给出一些树状数组的实现代码,希望读者能够仔细体会其中的细节。

【代码】

  求最小幂2^k:


int Lowbit(int t) 

    return t & ( t ^ ( t - 1 ) ); 

             
  求前n项和:


int Sum(int end) 

    int sum = 0; 
    while(end > 0) 
    { 
        sum += in[end]; 
        end -= Lowbit(end); 
    } 
    return sum; 


 对某个元素进行加法操作: 

void plus(int pos , int num) 

    while(pos <= n) 
    { 
          in[pos] += num; 
          pos += Lowbit(pos); 
    } 

免责声明:文章转载自《树状数组(转载)》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇支付宝支付私钥和公钥创建Sqlite—触发器(Trigger)下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Array数组

数组主要是用来 存储一组数据的: 1、掌握如何创建数组 2、掌握数组元素的读和写 3、掌握数组的length属性 创建数组的基本方式有两种: 1、使用Array构造函数 语法:new Array() new 是新建创建的意思 小括号()说明: (1)预先知道数组要保存的项目数量 (2)向Array构造函数中传递数组应包含的项。 <script>...

一篇文章教会你创建vue项目和使用vue.js实现数据增删改查

【一、项目背景】 在管理员的一些后台页面里,数据列表中都会对这些数据进行增删改查的操作,例如管理员添加商品、修改商品价格、删除商品、查询商品,我们应该关注这些数据的操作和处理。 【二、项目目标】 主要有以下5个目标: 1、如何创建vue项目。 2、数据添加方法:获取到id和name在data上面获取,组织一个对象,把对象通过数组的相关方法,添加到当前dat...

Oracle下定义和输出一个数组

分析: 首先要存在一个数组类型,然后才能去定义数组变量,所以编写PL/SQL如下: declare --定义一个数组类型 type a_type is table of number; a a_type := a_type(); begin a.extend(3); a(1) := 1; a(2) := 2; a(3) := 3...

js数组转为json

//数组var arr = new Array();arr.push({"id":1, "value":"a"});arr.push({"id":2, "value":"b"}); //jquery的方法,将数组转为json字符串var jsonStr = $.toJSON(arr); //jquery的方法,将json字符串转为json对象var jso...

Unity3D中常用的数据结构总结与分析

Unity3D中常用的数据结构总结与分析  c#语言规范 阅读目录 1.几种常见的数据结构 2.几种常见数据结构的使用情景 来到周末,小匹夫终于有精力和时间来更新下博客了。前段时间小匹夫读过一份代码,对其中各种数据结构灵活的使用赞不绝口,同时也大大激发了小匹夫对各种数据结构进行梳理和总结的欲望。正好最近也拜读了若干大神的文章,觉得总结下常用的数...

vue项目搜索历史功能的实现

播放器项目中歌曲搜素页面的 首先需要在state定义搜索历史,在其中保存搜索历史 state.js:// 搜索历史: searchHistory: []  mutations中新增改变搜索历史的方法 mutations.js:SET_SEARCH_HISTORY(state, history) { state.searchHistory = hi...