首页 > 其他分享 >第一课——树状数组

第一课——树状数组

时间:2024-03-15 17:22:36浏览次数:23  
标签:单点 树状 int lowbit 查询 第一课 数组

前缀和算法可以计算某一个区间的累记和,但是出现修改的时候,前缀和的效率便得不到保障。于是数状数组出现了。出现原因总结——需求从单纯的区间查询变为了单点修改 + 区间查询

树状数组

本文不探讨树状数组的开发过程,这里先给出树状数组的结构:
image
树状数组的设计非常巧妙,它让下标为\(i\)(从1开始)的位置存储原数组的部分和。比如下标为2的位置,存储了前两个数据的和,而下标为4的位置存储了前四个数据的和。
但是也有些特殊的位置,比如6。你会发现,虽然它是偶数下标,但是它并没有存储前6个数据,而是只存储了5、6两个数据。下面给出树状数组的核心机制\(lowbit\)。

lowbit()

\(lowbit\)的定义是:一个二进制数的低位零的个数。
比如,2 的二进制是 10 ,那么 \(lowbit(2)\) = 1 。
而 6 的二进制是110,所以和2一样 \(lowbit(6)\) = 1。
于是我们可以给出树状树组下标为 \(i\) 的位置的定义:

TreeArr[i] = 0;
for(int j = 0; j <= lowbit(i) ; j++)TreeArr[i] +=data[i-j];

这里我直接写了C++的代码,但是阅读应该没有困难。其中data是原数组的数据,TreeArr是我们构造的树状数组。

lowbit()函数的实现

\(lowbit\)函数有两种实现方式,其中第一种是比较容易理解的:

int lowbit(int i){
	return x - (x & (x - 1));
}

第二种是比较抽象的,但是我个人比较喜欢,因为它更加的简洁优美。

int lowbit(int i){
	return i & -i;
}

这个有兴趣的朋友可以自己推导一下,不过也不会很复杂的。

update(int i,int data)函数

然后我来看一下单点更新如何在树状数组这样奇怪的数据结构上实现。
首先这个操作传入两个参数,一个是在原数组的位置,另一个是修改后的数值。再来看一下树状数组的结构:
image
假设我data[3]加上一个1,那么树状数组中受到影响的节点有348,不难发现,我们可以从底部的3出发,自下而上的找出所有被影响的点,而4 = 3 + lowbit(3)8 = 4 + lowbit(4),是不是非常的妙?前推和后推都回到了\(lowbit\)上,不然怎么数\(lowbit\)是树状数组的核心机制呢?
有了这个理论基础,我们就能轻松的写出\(update\)函数的代码了。

int update(int i ,int data){
	while(i < n){				// n 是数组的长度
	TreeArr[i] += data;
	i += lowbit(i);
	}
}

不难看出这是一个O(lgN)的更新操作。

Sum(int j)函数

Sum(int j)函数实现了原数组中前j个数据的求和。
前面提到过,TreeArr[i]包括了从i开始的往前数lowbit(i)个数据的和,那么在求前 j个数据的和时,我们可以利用和更新中类似的方法,每次计算当前lowbit(j)个数据的和,然后去到j - lowbit(j)的位置继续求前面的值。代码如下,也是非常的简洁

int Sum(int j){
	int rest = 0;
	while(j > 0){
		rest += TreeArr[j];
		j -= lowbit(j);
	}
	return rest;
}

不难看出这是一个O(lgN)的求和。

应用:单点更新区间查询

P3374 【模板】树状数组 1
https://www.luogu.com.cn/problem/P3374

区间更新 + 单点查询

这时,我们的需求改变了,我们不再需要区间查询了,而只要单点查询,但是需要实现区间修改。这时我们可以使用到一个数学概念——差分。使用差分作为树状数组存储的内容,可以让树状树组从单点修改 + 区间查询变为区间修改 + 单点查询

差分的定义

假设d[n]是一个差分数组,那么:

\[d[n] = data[n] - data[n-1] \]

非常好理解,如果我要修改全部的数据,比如把所有数据加 1,那么我们只需要在第一个位置加上一就好了,因为差分数组的性质,其他的位置值并不需要修改。
那么如果我们要进行单点的查询,比如 data[n](原数据),就需要计算前d数组的前n项和。这一就完美地完成了从单点修改 + 区间查询变为区间修改 + 单点查询的过程。

树状数组的不足

当我们的问题变成区间修改 + 区间查询时,树状树组便不能完成这个工作了(一维),我们需要更好的数据结构,下节课——线段树完美解决树状数组的问题。
PS:其实并不是,树状树组的内存是(N),而线段树需要(N<<2)也就是原数组四倍的内存空间。

标签:单点,树状,int,lowbit,查询,第一课,数组
From: https://www.cnblogs.com/zhywyt/p/18075786

相关文章

  • Java题目-数组计算-中位数- 圆类的构造-时间计算-学生类设计
    第一题:数组计算题目描述:编写Java程序,计算两个整型数组的和、差、乘积、商的整数部分及大小关系。定义如下:和:两个数组对应元素的和,若元素缺失,则补0;差:第一个数组和第二个数组对应元素的差,若元素缺失,则补0;乘积:两个数组对应元素的积,若元素缺失,则计0;除:第一个数组元素除以第二......
  • 查分数组
    //差分数组工具类classDifference{//差分数组privateint[]diff;/*输入一个初始数组,区间操作将在这个数组上进行*/publicDifference(int[]nums){assertnums.length>0;diff=newint[nums.length];//根据初始......
  • MATLAB学习笔记1.数组运算
    先来介绍两个常用的,在命令行里边输入“clc”,就会清空以上的命令行(也就是这个直接与你对话的地方)的所有内容;但是并不会把已经设置的变量清空,要想清空变量,则需要在命令行中输入“clear”,这样就可以把右侧已经设置的变量都清空掉了。下面是示例输入回车再输入“clear”并输入......
  • 108. 将有序数组转换为二叉搜索树c
    /***Definitionforabinarytreenode.*structTreeNode{*intval;*structTreeNode*left;*structTreeNode*right;*};*/structTreeNode*build(int*nums,inthead,inttail){if(head>tail)returnNULL;intmid=head+(......
  • golang 随机数组的性能对比测试
    最近需要用到随机数,但在随机数的生成方面遇到些问题,如加了seed后反而生成的数组是固定的,没有加是随机的,后面查资料了解到,如果seed值是一样的,序列中的值就固定的,而不加seed时,每次的都是随机的,后面想到如果用来做负载均衡呢,性能又如何。下面是源码:packagebenchimport( ......
  • 26. 删除有序数组中的重复项
    给你一个非严格递增排列的数组nums,请你原地删除重复出现的元素,使每个元素只出现一次,返回删除后数组的新长度。元素的相对顺序应该保持一致。然后返回nums中唯一元素的个数。classSolution{publicintremoveDuplicates(int[]nums){if(nums==n......
  • 稀疏数组与二维数组之间的转换
    稀疏数组介绍:稀疏数组:当一个数组中大部分元素为同一个值时,就可以考虑使用稀疏数组来保存数据节省空间。稀疏数组的原理:1)稀疏数组一共三列,第一行的第一列保存原二维数组的行数,第一行第二列保存原二维数组的列数,第一行第三列保存原二维数组非0数据的个数;2)稀疏数组一共有【原二维......
  • 【算法】二分查找——在排序数组中查找元素的第一个和最后一个位置
    leetcode链接题目描述给你一个按照非递减顺序排列的整数数组nums,和一个目标值target。请你找出给定目标值在数组中的开始位置和结束位置。如果数组中不存在目标值target,返回[-1,-1]。你必须设计并实现时间复杂度为O(logn)的算法解决此问题。示例1:输入:nums=[5,......
  • 树状数组
    树状数组简介树状数组维护信息的类型:树状数组一般用来维护可差分的信息比如:累加和、累加乘或者出题人出了某个可差分的信息不可差分的信息:比如最大值、最小值不可差分的信息一般不用树状数组来维护,而会选择线段树来维护,因为线段树维护的思考难度低大多数情况下,线段树可以......
  • 一维数组_校门外的树
    任务描述某校大门外长度为L的马路上有一排树,每两棵相邻的树之间的间隔都是1米。我们可以把马路看成一个数轴,马路的一端在数轴0的位置,另一端在L的位置;数轴上的每个整数点,即0,1,2,……,L,都种有一棵树。由于马路上有一些区域要用来建地铁。这些区域用它们在数轴上的起始点和终止点表......