ACM 树形数组
树状数组(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 树形数组的更多相关文章
- PHP 发布两个不用递归的树形数组构造函数(转)
<?php/** *创建父节点树形数组 * 参数 $ar 数组,邻接列表方式组织的数据 $id 数组中作为主键的下标或关联键名 $pid 数组中作为父键的下标或关联键名 * 返回 多维数组 ** ...
- 树形数组 java
2^k求法 int lowbit(int x) { return x&(-x); } lowbit()的返回值就是 2^k 次方的值. 基本树形数组的模板 import java.util.* ...
- PHP递归生成树形数组
数据表结构 id name pid ){ foreach($data as $row){ if($row['pid']==$p_id){ $tmp = $this->tree( ...
- element-ui 格式化树形数组在table组件中展示(单元格合并)
最近做的项目涉及到很多单元格合并问题,element-ui组件对于单元格合并的处理虽然很灵活,但是需要事先计算好每个单元格合并的rowspan和colspan,直接在span-method属性中计算实 ...
- LA 4329 Ping pong 树状数组
对于我这样一名脑残ACMer选手,这道题看了好久好久大概4天,终于知道怎样把它和“树状数组”联系到一块了. 树状数组是什么意思呢?用十个字归纳它:心里有数组,手中有前缀. 为什么要用树状数组?假设你要 ...
- php 实现树形结构
<?phpclass Tree{ private $OriginalList; public $pk;//主键字段名 public $parentKey;//上级id字段名 public $ch ...
- js treeData 树形数据结构 无限层级(转载)
js实现无限层级树形数据结构(创新算法) 转载:https://blog.csdn.net/Mr_JavaScript/article/details/82817177 由于做项目的需要,把一个线性数 ...
- PHP数组操作类
class ArrayHelper{ /** * 从数组中删除空白的元素(包括只有空白字符的元素) * * 用法: * @code php ...
- 数组-LeetCode-笔试
目录 数组理论基础 二分查找 二分法第一种写法 二分法第二种写法 ACM 移除元素 暴力解法 双指针法(快慢指针) ACM 有序数组的平方 暴力排序 双指针法 长度最小的子数组 暴力解法 滑动窗口 相 ...
随机推荐
- 建立、配置和使用Activity——启动、关闭Activity
一个Android应用通常都会包含多个Activity,但只有一个Activity会作为程序的入口——当该Android应用运行时将会自启动并执行该Activity.至于应用中的其他Activity, ...
- RAC执行root.sh报libcap.so.1: cannot open shared object file
Failed to create keys in the OLR, rc = 127, Message: /opt/app/11.2.0/grid/bin/clscfg.bin: error whil ...
- JavaScript 44 Puzzlers
http://mp.weixin.qq.com/s?__biz=MzAxODE2MjM1MA==&mid=2651550987&idx=1&sn=f7a84b59de14d0b ...
- HMX-Server C++ 分步式服务器大版本更新了(有源码)
原文地址:http://www.cnblogs.com/hellohuang/p/6294763.html # HMX-ServerHMX-Server分步式服务器框架,主要分为网关.登录.世界.场景 ...
- Redis 提供的好的解决方案 实例
1.Redis 支持的String 类型的,它是 二进制的,比较安全,当有一些 图片或者css文件影响运行速度的时候,可以缓存之.
- 字符集编码与Python(二)Unicode与utf-8
Python中的Unicode和utf-8 上一篇提过了字符集的历史其中简单的讲解了Unicode与utf-8的关系,简单的总结一下: utf-8和utf-16 .utf-32是一类,实现的功能是 ...
- ADO.NET 扩展属性、配置文件 和 对战游戏
扩展属性 有外键关系时将信息处理成用户可看懂的 利用扩展属性 如:Info表中的民族列显示的是民族代号处理成Nation表中的民族名称 需要在Info类里面扩展一个显示nation名称的属性 例:先前 ...
- MySQL插入数据中文乱码问题的解决
一.使用语句 show variables like 'character%'; 来查看当前数据库的相关编码集. 1.启动cmd,登录mysql ①cd C:\Program Files\MySQL\ ...
- Java之英格玛简单实现以及加密验证码的应用
最近看了一部电影<模仿游戏>,<模仿游戏>中艾伦·图灵破译英格玛让我对英格玛产生了好奇,于是就开始翻阅资料对其进行研究,但是毕竟智慧有限,所以我这里用Java实现一个简单的英格 ...
- 读书笔记 effective c++ Item 2 尽量使用const,枚举(enums),内联(inlines),不要使用宏定义(define)
这个条目叫做,尽量使用编译器而不要使用预处理器更好.#define并没有当作语言本身的一部分. 例如下面的例子: #define ASPECT_RATIO 1.653 符号名称永远不会被编译器看到.它 ...