树状数组(Binary Indexed Tree(BIT), Fenwick Tree)是一个查询和修改复杂度都为log(n)的数据结构。主要用于查询任意两位之间的所有元素之和,但是每次只能修改一个元素的值;经过简单修改可以在log(n)的复杂度下进行范围修改,但是这时只能查询其中一个元素的值。

树状数组的解法和程序网上有很多,这里我想思考一下这种算法的灵魂,也就是基于什么样的契机和灵感产生了这种绝妙的想法。这是我感兴趣的方向。

这种算法,主要用于查询数组中任意两个数之间的所有元素之和,而且这个数组我们会经常修改里面任意的数。

如果抛弃修改数组这个操作,也就是原数组是不变的,只是做查询的话,我们会很容易想到一种方法。

设 原数组 a[n],我们可以构造另一个数组c[n],取任意下标i,让 c[i] = a[1] + a[2] + …… + a[i]

这样如果我们要计算 下标k和j之间的数的和(包括k和j),sum = c[j] - c[k-1]

因此只要构建了数组c[n],查询操作的时间复杂度都是 o(1) 级别的,非常快

如果加入了修改操作的话,上边的方法就不太适合了,因为如果 修改了 a[i],对应的 c[i]、c[i+1]、……、c[n],都要因此修改。这修改后的时间复杂度是 o(n) 级别的,虽然查询操作还是 o(1) ,但如果修改操作很多,这种方法显然不适合。

如果说在这种方法之上改进一下,让修改操作的时间复杂度减少,查询操作时间复杂度增加,树状数组这种算法就做到了这一点,让 修改和查询的时间复杂度都统一为o(log(n)),log(n)的好处是n越大,带来的效率优化就越高

让我们看下改进后的区别

原来:

c[1] = a[1]
c[2] = a[1] + a[2]
c[3] = a[1] + a[2] + a[3]
...
c[n] = a[1] + a[2] + a[3] + …… + a[n]

改进后:

c[1] = a[1]
c[2] = a[1] + a[2]
c[3] = a[3]
c[4] = a[1] + a[2] + a[3] + a[4]
c[5] = a[5]
c[6] = a[5] + a[6]
c[7] = a[7]
c[8] = a[1] + a[2] + a[3] + a[4] + a[5] + a[6] + a[7] + a[8]
...
c[n] = a[n – 2^k + 1] + ... + a[n] 【其中k为n二进制末尾0的个数】

从改进后的规律来看,如果我们修改了a[i],我们就不用将所有c[i]之后的元素都修改了,而是有所选取的修改。这个修改的时间复杂度是o(log(n)),可以从n – 2^k + 1这个下标公式看出来。

用一张树状图来表示的话会非常直观

查询操作的话,以计算前n个数的和为例,

sum(7) = c[7] + c[6] + c[4] = c[0x111] + c[0x110] + c[0x100]

sum(10) = c[10] + c[8] = c[0x1010] + c[0x1000]

sum(13) = c[13] + c[12] + c[8] = c[0x1101] + c[0x1100] + c[0x1000]

看到这里聪明的你应该会发现一些规律,以7的二进制 0x111 为例,从右到左逐渐去掉1,  0x110,0x100 也就是 6 和 4,因此得到 c[7] + c[6] + c[4]

用这种方法就能求出前n个数的和,然后如果我们要计算 下标k和j之间的数的和(包括k和j),sum = c[j] - c[k-1]

程序如下:

#include <stdio.h>
//#include <windows.h>
#include <string.h> #define MAX 1000001 int c[MAX]; // 用此方法可以计算二进制数从右到左数第一个1出现的数
// 例子:
// 1————1
// 10————10
// 110100————100
// 10111————1
// 10000————10000
int lowbit(int t)
{
return t&(-t);
//return n&(n^(n-1));
} // 修改数组中某个数,delta是增量
void modify(int n, int delta)
{
while(n <= MAX)
{
int d;
c[n] += delta;
n += lowbit(n);
}
} // 求前n个数的和
int sum(int n)
{
int result = ;
while(n != )
{
result += c[n];
n -= lowbit(n);
}
return result;
} int main()
{
int N = , M = , i = ;
scanf("%d %d", &N, &M);
memset(c, , sizeof(c));
while(N--)
{
int temp;
scanf("%d", &temp);
modify(i++, temp);
}
while(M--)
{
char s[];
int I = , A = , num;
scanf("%s %d %d", &s, &I, &A);
if(strcmp(s, "QUERY") == )
{
num = sum(A) - sum(I-);
printf("%d\n", num);
}
else if(strcmp(s, "ADD") == )
{
modify(I, A);
}
}
//system("pause");
}

总结:在无修改操作的情况下,我们用c[n]来表示数组a中前n个数的和sum(n),在有修改操作的情况下,我们还是用数组c中的数来表示sum(n),不同的是,这里的会用到多个数如 c[i]、c[j]、c[k]……,而如何通过n来找到i,j,k……这些相关数和构造出数组c,就是树形数组这种算法的关键所在——将数n分解为2的次方数,如2、4、8、16。

ACM 树形数组的更多相关文章

  1. PHP 发布两个不用递归的树形数组构造函数(转)

    <?php/** *创建父节点树形数组 * 参数 $ar 数组,邻接列表方式组织的数据 $id 数组中作为主键的下标或关联键名 $pid 数组中作为父键的下标或关联键名 * 返回 多维数组 ** ...

  2. 树形数组 java

    2^k求法 int lowbit(int x) { return x&(-x); } lowbit()的返回值就是 2^k 次方的值. 基本树形数组的模板 import java.util.* ...

  3. PHP递归生成树形数组

    数据表结构 id   name  pid       ){ foreach($data as $row){ if($row['pid']==$p_id){ $tmp = $this->tree( ...

  4. element-ui 格式化树形数组在table组件中展示(单元格合并)

    最近做的项目涉及到很多单元格合并问题,element-ui组件对于单元格合并的处理虽然很灵活,但是需要事先计算好每个单元格合并的rowspan和colspan,直接在span-method属性中计算实 ...

  5. LA 4329 Ping pong 树状数组

    对于我这样一名脑残ACMer选手,这道题看了好久好久大概4天,终于知道怎样把它和“树状数组”联系到一块了. 树状数组是什么意思呢?用十个字归纳它:心里有数组,手中有前缀. 为什么要用树状数组?假设你要 ...

  6. php 实现树形结构

    <?phpclass Tree{ private $OriginalList; public $pk;//主键字段名 public $parentKey;//上级id字段名 public $ch ...

  7. js treeData 树形数据结构 无限层级(转载)

    js实现无限层级树形数据结构(创新算法) 转载:https://blog.csdn.net/Mr_JavaScript/article/details/82817177 由于做项目的需要,把一个线性数 ...

  8. PHP数组操作类

    class ArrayHelper{           /**      * 从数组中删除空白的元素(包括只有空白字符的元素)      *      * 用法:      * @code php ...

  9. 数组-LeetCode-笔试

    目录 数组理论基础 二分查找 二分法第一种写法 二分法第二种写法 ACM 移除元素 暴力解法 双指针法(快慢指针) ACM 有序数组的平方 暴力排序 双指针法 长度最小的子数组 暴力解法 滑动窗口 相 ...

随机推荐

  1. C# backgroundworker使用方法

    # BackgroundWorker 控件的几个实例(C# backgroundworker使用方法): 在 WinForms 中,有时要执行耗时的操作,在该操作未完成之前操作用户界面,会导致用户界面 ...

  2. spring mvc 下载文件链接

    http://www.blogjava.net/paulwong/archive/2014/10/29/419177.html http://www.iteye.com/topic/1125784 h ...

  3. SVG六基本元素

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  4. 如何快速定位到Eclipse自动添加的TODO

    把自动生成的// TODO ....前面加上todo,这样生成之后就会有编译错误,直接 ctrl+. 就到该位置了,可以删除todo留着// TODO ...,也可以ctrl+d删除一行: 不建议不生 ...

  5. vue原来可以这样上手

    今儿与一群友讨论vue相关问题让我思量极深,1.我们是否在争对性解决问题或者说是帮助别人:2.我们是否在炫耀自己的技能.以下是被戏剧化的对白: "群友":最近按照vue官网示例学习 ...

  6. jquery常用的一些方法

    一.选择网页元素(标签选择器) $(document) //选择整个文档对象 $('#myId') //选择ID为myId的网页元素 $('div.myClass') // 选择class为myCla ...

  7. [html5] 学习笔记-编辑 API 之 Range 对象(一)

    1.Range对象的基本概念 一个Range对象代表页面上的一段连续区域,通过Range对象,可以获取或修改网页上的任何区域. <!DOCTYPE html> <html> & ...

  8. 光荣与梦想 | XMove动作捕捉系统(一)

    XMove是我和几个死党从2010年开始开发的一套人体动作捕捉系统,软硬件全部自行开发,投入了大量的精力,历经三年,发展四个版本. 今年春节回到老家,翻出了2011年春节时焊电路用过的松香和和硬盘角落 ...

  9. Linux驱动技术(七) _内核定时器与延迟工作

    内核定时器 软件上的定时器最终要依靠硬件时钟来实现,简单的说,内核会在时钟中断发生后检测各个注册到内核的定时器是否到期,如果到期,就回调相应的注册函数,将其作为中断底半部来执行.实际上,时钟中断处理程 ...

  10. matlab 中max函数用法

    Matlab中max函数在矩阵中求函数大小的实例如下:(1)C = max(A)返回一个数组各不同维中的最大元素.如果A是一个向量,max(A)返回A中的最大元素.如果A是一个矩阵,max(A)将A的 ...