特点

1. 针对 数组连续子序列累加和 问题(需要进行频繁的 update、sum 操作);

2. 并非是树型结构,只是逻辑上层次分明;

3. 可以通过 填坑法 来理解;

4. 中心思想:每一个整数都可以由几个 二进制指数的相加和 来进行唯一表示。

中心思想

每一个整数都可以由几个二进制指数的相加和唯一表示:

11    =  2^3  +  2^1  +  2^0
01011 = 01000 + 00010 + 00001 //二进制表示

在Binary Indexed Tree 中,上述的思想应用体现在:

我们需要下标为 1~11 的元素的累加和, 要 1~8 的累加和,加上 9~10 的累加和, 最后加上 11 的累加和。

填坑法图解 Binary Indexed Tree

数组例子:

数组:[ 0, 2, 0, 1, 1, 1, 0, 4, 4, 0, 1, 0, 1, 2, 3, 0 ]
下标: 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

为了简化后续操作,我们将上例中数组下标改为从 1 开始。

第一层:在1~16区间内,在下标为 2的指数 的BIT数组元素位置中,填充  从1开始所有下标为  2的指数(1, 2,4,,8,16) 的区间和。

第二层:在所有未填充区间中,设每个区间开头下标为 start,在下标为 start+2的指数 的BIT数组元素位置中,填充 从start开始下标为 start+2的指数 的区间和。

第三层,依照第二层的填坑方法类推,填充 7~7, 11~11, 13~15 区间。

第四层,以此类推,填充最后一个 坑 15~15。

从上述过程可以看出,Binary Indexed Tree 实际上是个层次分明的结构。

填坑法的角度来看,就是在每个坑中的第1、2、4、8、16……个位置上,填入从 坑的 start 位置 到 该位置 上所有元素的 sum 值;

第一层只有一个大坑,而第二层的坑 则是第一层填坑后留下的所有空洞位置;第三层的坑则是第一层和第二层填补后留下的所有空洞位置……

下标的二进制表示来看,坑就是 0 的位置(多个 0 聚一起形成一个大坑):

第一层      00000 (这里假设第一层是一个大坑)
填坑   0000 (1)
       0000 (2)
       0000 (4)
       0000 (8)
       0000 (16)
第二层   00001          00010           00100            01000            10000 (第二层有3个坑)
--------------------------------------------------------------------------------------------- 填坑    无 0001(3) 0010 (5) 0100 (9) 无
0010 (6) 0100 (10)
0100 (12)
……

注意区分几个概念:

1. 坑是指什么?从什么位置开始,到什么位置结束?-->下标二进制表示中 0 的聚合形成坑

2. 在BIT数组中填充的究竟是什么? --> 某个坑中从 坑的开始位置 到 该BIT数组下标位置 的区间和

3. 如果确定每一层要操作的BIT数组下标?
--> 可以根据下标的二进制表示,先确定坑有多大,再把坑中最后一个0变为1,然后把这个1依次左移,直到到达坑的开始位置;
就可以得到一系列需要填充的区间结束位置,结合坑的开始位置,计算出该区间的和,填入 BIT[区间结束位置] 中。

Sum 操作

数字操作原理:

15 -> 14 -> 12 ->8
01111 -> 0111 -> 0110 -> 0100 //从右往左,依次把 1 反转为 0
15 = 01000 (1-8) + 00100 (9-12) + 00010 (13-14) + 00001 (15)

代码实现:

求 1~K 区间和
从 K 开始,依次翻转 K 的二进制表示 的最后一个 1 来获取 K1, K2, ……直至归零,然后:
Sum(1~K) = BIT[K] + BIT[K1] + BIT[K2] + ......

翻转最后一个 1 的小技巧:

利用补码
15的补码: 00001111
-15的补码: 11110001
两者按位相与,得到 00000001,即15二进制表示的最后一位1
所以直接让 15 减去 得到的这最后一位1就好。

所以推导下一位的代码是:

BIT[K] = BIT[K - (K & -K)]

时间复杂度

O(logn)

 Update 操作

对数组下标6的元素 +2示例:

数字操作原理:

6 -> 8 ->16
00110 -> 01000 -> 10000
00110 + 00010 = 01000
01000 + 01000 = 10000
//让 K 加上其二进制表示的最后一位 1 形成的数字,即可推导出下一项的下标

推导下一项代码实现:

BIT[K] = BIT[K + (K & -K)]

时间复杂度:

O(logn)

Binary Indexed Tree的建立

把初始数组设为全为 0 的数组,然后依次把数组中的元素进行一次 update操作,就完成了 BIT 数组的构建。

时间复杂度:

O(nlogn)

其他观察

以 1 为初始下标,BIT数组中的奇数下标的元素都是直接存了原数组元素的值,而偶数下标的原数组的值也可以通过BIT数组在比较小的消耗情况下得到。

所以,Binary Indexed Tree在一定效率容忍情境下,可以用BIT数组取代原数组,不必保留原数组(省去空间)

BIT数组还可以被拓展到多维的应用????(后续看到相关资料将补充)

参考链接:https://www.cnblogs.com/whensean/p/6851018.html

Binary Indexed Tree 总结的更多相关文章

  1. Leetcode: Range Sum Query 2D - Mutable && Summary: Binary Indexed Tree

    Given a 2D matrix matrix, find the sum of the elements inside the rectangle defined by its upper lef ...

  2. SRM 627 D1L2GraphInversionsDFS查找指定长度的所有路径 Binary indexed tree (BIT)

    题目:http://community.topcoder.com/stat?c=problem_statement&pm=13275&rd=16008 由于图中边数不多,选择DFS遍历 ...

  3. 树状数组(Binary Indexed Tree,BIT)

    树状数组(Binary Indexed Tree) 前面几篇文章我们分享的都是关于区间求和问题的几种解决方案,同时也介绍了线段树这样的数据结构,我们从中可以体会到合理解决方案带来的便利,对于大部分区间 ...

  4. Binary Indexed Tree (Fenwick Tree)

    Binary Indexed Tree 主要是为了存储数组前缀或或后缀和,以便计算任意一段的和.其优势在于可以常数时间处理更新(如果不需要更新直接用一个数组存储所有前缀/后缀和即可).空间复杂度O(n ...

  5. Hdu5921 Binary Indexed Tree

    Hdu5921 Binary Indexed Tree 思路 计数问题,题目重点在于二进制下1的次数的统计,很多题解用了数位DP来辅助计算,定义g(i)表示i的二进制中1的个数, $ans = \su ...

  6. Binary Indexed Tree

    我借鉴了这个视频中的讲解的填坑法,我认为非常易于理解.有FQ能力和基本英语听力能力请直接去看视频,并不需要继续阅读. naive 算法 考虑一个这样的场景: 给定一个int数组, 我们想知道它的连续子 ...

  7. 树状数组(Binary Indexed Tree)

    树状数组(Binary Indexed Tree,BIT) 是能够完成下述操作的数据结构. 给一个初始值全为 0 的数列 a1, a2, ..., an (1)给定 i,计算 a1+a2+...+ai ...

  8. Fenwick Tree / Binary Indexed Tree

    Motivation: Given a 1D array of n elements. [2, 5, -1, 3, 6] range sum query: what's the sum from 2n ...

  9. Binary Indexed Tree 2D 分类: ACM TYPE 2014-09-01 08:40 95人阅读 评论(0) 收藏

    #include <cstdio> #include <cstdlib> #include <climits> #include <cstring> # ...

随机推荐

  1. window下svn开机自动启动

  2. Java基础5一数组的常见应用算法

    常用算法 1.冒泡排序: 原理:比较两个相邻的元素,将值大的元素交换至右端 示例: public static void bubbleSort(int[] a) { int n = a.length; ...

  3. hdu1829 A Bug's Life 基础种类并查集

    题目的大意可以理解为:A爱B,B爱C ……给出一系列爱恋的关系,推断有没有同性恋. 思路是把相同性别的归为一个集合(等价类),异性的异性为同性. #include<iostream> #i ...

  4. c++ windows与linux通信中文乱码问题解决方法

    在linux中默认编码方式是UTF-8,在Windows下默认编码方式时GB2312.因此,在Windows和Linux进行通信的时候,如果没有进行转码则会出现乱码问题.因此,需要进行UTF-8和GB ...

  5. MSSQL_20160719_在作业步骤中使用sp_send_dbmail遇到的问题

    需求: 在作业步骤中使用sp_send_dbmail发出邮件, 并将数据库中的日志表通过@query参数导出文本作为邮件附件 遇到错误: 服务器 DB-DWH-1,第 1 行  服务器主体 " ...

  6. JS中常见问题

    //s金额 n保留几位小数 默认保留两位小数 s代表金额,n代表保留的小数位数 function formatMoney(s, n) { n = n > 0 && n <= ...

  7. 01--[转]C++强大背后

    [转]C++强大背后 2014-01-22 分类:互联网 阅读(9295) 评论(6) 在31年前(1979年),一名刚获得博士学位的研究员,为了开发一个软件项目发明了一门新编程语言,该研究员名为Bj ...

  8. 为什么java io流必须得关闭

    当我们new一个java流对象之后,不仅在计算机内存中创建了一个相应类的实例对象.而且,还占用了相应的系统资源,比如:文件句柄.端口.数据库连接等.在内存中的实例对象,当没有引用指向的时候,java垃 ...

  9. spring rest docs自定义代码片段

    Spring rest docs 文档插件在生成文档时会默认生成6个代码片段,自适应生成其它片段.通过阅读官方文档发现其可以自定义生成的代码片段,但是官方只说了可以自定义模版,修改现有的代码片段的方法 ...

  10. vue学习笔记——脚手架安装

    项目启动:npm run build 脚手架 vue cli vue cli --2 安装命令 #全局安装 npm install --global vue-cli #卸载vue-cli  npm u ...