深入理解数据结构 —— 树状数组

news2024/11/19 6:28:38

什么是树状数组

我们知道,前缀和数组能解决任意一段区间的累加和问题

但这建立在数组中的元素不发生变化的情况,如果可以修改原始数组中的某个元素,为了让前缀和数组正确,就需要在前缀和数组中修改该元素位置后面的所有的数,时间复杂度为O(N)

而树状数组能做到查询区间和,修改单个元素都为O(logN)

前缀和树状数组
区间查询O(1)O(logN)
修改单个元素O(N)O(logN)

因此,树状数组专门解决带单点更新的区间累加和需求

结构

对于长度为n的原数组data,生成一个长度为n+1的tree数组

public  class IndexTree {
    // 原始数组
    private  int[] data;
    // tree数组
    private  int[] tree;
    private  int size;

 public IndexTree(int[] data) {
        this.data = data;
        this.size = data.length;
        this.tree = new  int[size+1];
    } 
}

为什么tree长度为n+1?第tree数组中第0位不用

tree中的每一项i,其实代表一个范围的累加和,代表哪个范围呢?

将i的二进制数,抹去最后一个1,再加1,记为newi

代表原始数组中第newi个数,到第i个数和

什么叫抹去最后一个1?即减去二进制下从右往左数的第一个1

例如1111抹去最后一个1变为1110

10110抹去最后一个1变为10100

例如:i = 12

  • 其二进制表示为1100
  • 抹去最后一个1(即100)为1000
  • 加1得到1001

那么tree[i]的值为,原数组中第1001(十进制为9)个数到第i个数,也就是1100(十进制为12),这些数的和

根据这个规则,我们看看从tree中,下标为1到16的数,代表原数组中哪些数的累加和

tree中的下标i下标的二进制表示减去最后一个1再加1下标本身含义(二进制表示)
11011第1个数到第1个数的和
2100110第1个数到第10个数的和
311101111第11个数到第11个数的和
410001100第1个数到第100个数的和
5101100101101第101个数到第101个数的和
6110100101110第101个数到第110个数的和
7111110111111第111个数到第111个数的和
81000011000第1个数到第1000个数的和
91001100010011001第1001个数到第1001个数的和
101010100010011010第1001个数到第1010个数的和
111011101010111011第1011个数到第1011个数的和
121100100010011100第1001个数到第1100个数的和
131101110011011101第1101个数到第1101个数的和
141110110011011110第1101个数到第1110个数的和
151111111011111111第1111个数到第1111个数的和
16100000110000第1个数到第10000个数的和

在这里插入图片描述

前缀和

如果我想求原始数组中,从第一个数开始到第i个数的累加和,应该怎么求?

假设i为45,其二进制表示为101101

准备一个累加和变量res

  • 首先从tree数组中找到下标为101101的值,加到res中,即res += sum[101101]
  • i抹去最后一个1,变为101100,res += tree[101100]
  • 再抹去最后一个1,变为101000,res += tree[101000]
  • 再抹去最后一个1,变为100000,res += tree[100000]

此时如果再抹去最后一个1,i将变为0,所以停止

总结规律:不断抹去i的最后一个1,sum[i]累加到结果中

public  int sum(int i) {
    int res = 0;
    while (i > 0) {
        res += tree[index];
        // 抹去最右侧的1
        i -= i & (~i + 1);
    }
    return res;
}

正确性证明

为什么这么做,能正确计算出前缀和呢?

以i = 45 (101101)为例,我们依次看抹去最后一个1后的值在sum数组中代表什么:

isum[i]表示的起始位置sum[i]表示的结束位置
没有抹去101101101101101101
第一次抹去101100101001101100
第二次抹去101000100001101000
第三次抹去1000001100000

可以发现,这4次的i值在sum数值中所代表的的区间和,不重不漏地覆盖了从1到101101的所有数

  • sum[100000]:从第1个数到第100000个数的累加和
  • sum[101000]:从第100001个数到第101000个数的累加和
  • sum[101100]:从第101001个数到第101100个数的累加和
  • sum[101101]:从第101101个数到第101101个数的累加和

将sum数组中这4个数累加起来,恰好就能得到从第1到第101101个数的前缀和

时间复杂度

每次while循环抹去最右侧的1,最多抹去logN次,因此时间复杂度为O(logN)

单点增加值

假设将i位置的数加上v

当修改原始数组中某个数时,需要同时修改sum数组,怎么知道在sum数组中哪些数受牵连呢?

例如当size = 16,我修改第3个数时,3的二进制表示为11

  • 将11加上最右侧的1,得到100,sum[110] += v
  • 将100加上最右侧的1,得到1000,sum[1000] += v
  • 将1000加上最右侧的1,得到10000,大于size,结束循环

这些位置的数,都是因为原始数组中第i个数变化了,需要调整的位置

总结规律:不断将i加上最后一个1,sum[i] += v,直到i大于size为止

public  void add(int i,int v) {
    while (i <= size) {
        tree[i] += v;
        // i加上最右侧的1
        i += i & (~i + 1);
    }
}

时间复杂度

每次while循环加上最右侧的1,其实没加几次就会开始每次循环翻倍,最多加logN 次,因此时间复杂度为O(logN)

初始化tree数组

根据原始数组初始化tree数组时,复用add方法就行:

先假设原始数组全为0,依次给每个位置i增加data[i]的值,就等于初始化好了tree数组

public IndexTree(int[] data) {
    this.data = data;
    this.size = data.length;
    this.tree = new  int[size+1];

    for (int i = 1;i<=size;i++) {
        add(i, data[i-1]);
    }
}

单点修改值

现在可以实现将某个点的值增加v,也可以复用该方法将某个点的值修改成d

  1. 先计算d和原始值的
  2. 调用add方法将原始值增加这个差
public  void set(int i,int d) {
   int diff = d - data[i-1];
   add(i, diff);
}

计算区间和

有了前缀和,计算区间和的就方便了

public  int rangeSum(int left,int right) {
    return sum(right) - sum(left-1);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/147586.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数组模拟实现单链表、双链表、栈、队列

文章目录 前引 一、数组模拟实现单链表 1、1 数组模拟的单链表解析 1、2 数组模拟实现单链表例题 二、数组模拟实现双链表 2、1 数组模拟实现双链表解析 2、2 数组模拟实现双链表例题 三、数组模拟实现栈 3、1 数组模拟实现栈解析 3、2 数组模拟实现栈例题 四、数组模拟实现队…

【C++学习】基础语法(一)

1、背景知识 1.1 什么是C C语言是结构化和模块化的语言&#xff0c;长用于处理较小规模的程序&#xff1b;对于规模较大、问题复杂的程序&#xff0c;则需要高度的抽象和建模&#xff0c;此时C语言不合适处理这类问题。为了解决此类影响软件的问题&#xff0c;20世纪80年代&am…

Python | Matplotlib | 不完全总结

本文对 Matplotlib 的用法进行不完全总结。 更新&#xff1a; 2023 / 1 / 4 Python | Matplotlib | 不完全总结ImageMagick导库画布创建多子图动图2D柱状图基本&#xff1a;水平 / 垂直柱形主题&#xff1a;颜色、文字、网格线动图线图基本动图3D柱状图基本线图动图参考链接Im…

Web文件操作:上传与下载

文件上传与下载文件上传文件上传的实现表结构设计UploadFileServiceImpl的实现上传文件遇到的问题与解决文件下载文件上传 文件上传的表单中&#xff0c;需要注意的三个地方&#xff1a; 1.表单的请求方式必须为post&#xff1b; 2.表单域必须有file类型&#xff1b; 3.表单的e…

活动星投票奋斗青春,使命必达网络评选微信的投票方式线上免费投票

“奋斗青春&#xff0c;使命必达”网络评选投票_如何进行投票推广_参与投票活动_小程序的投票发展现在来说&#xff0c;公司、企业、学校更多的想借助短视频推广自己。通过微信投票小程序&#xff0c;网友们就可以通过手机拍视频上传视频参加活动&#xff0c;而短视频微信投票评…

英伟达528.02驱动发布支持4070 Ti!GFE新增9款游戏

自GTX 4070 Ti显卡发售后&#xff0c;英伟达便随即发布了支持该新显卡的Game Ready 528.02驱动&#xff0c;同时为《战意》和《达喀尔沙漠拉力赛》两款新游戏带来DLSS 3的支持&#xff0c;DLSS 3的队伍再度壮大&#xff01; 驱动人生现已支持英伟达Game Ready 528.02驱动&…

围绕http请求头中Referer展开的一些知识

1. 什么是referer&#xff1f; <点击以获取跳转信息 >跳转过去记得按一下f12点击网络请求详情&#xff0c;再刷新一下&#xff0c;就可以看见referer字段&#xff1a; 当我们尝试在浏览器内部直接输入这熟悉的网址时&#xff0c;此时刷新后则是这样一番景象&#xff1…

C++类和对象的基本概念

目录 1.c和c中struct的区别 2.类的封装 3.类的访问权限 1.c和c中struct的区别 c语言中结构体中不能存放函数,也就是数据(属性)和行为(方 法)是分离的 c中结构体中是可以存放函数的,也就是数据(属性)和行为 (方法)是封装在一起的 #define _CRT_SECURE_NO_WARNINGS #include …

基于Python tensorflow机器学习的人脸识别登陆系统源码、人脸注册系统源码

face_login 代码下载地址&#xff1a;基于Python tensorflow机器学习的人脸识别登陆系统源码、人脸注册系统源码 介绍 本项目基于tensorflow机器学习&#xff0c;实现web端人脸识别登陆&#xff0c;人脸注册。 提供手机端页面(face_login_app)和网页端页面(vue_element-adm…

JUC并发编程学习笔记(六)线程池及分支合并框架

10 ThreadPool 线程池&#xff08;重点&#xff09; 10.1 线程池简介 回顾以前的连接池概念 连接池是创建和管理一个连接的缓冲池的技术&#xff0c;这些连接准备好被任何需要它们的线程使用 线程池&#xff08;英语&#xff1a;thread pool&#xff09;&#xff1a;一种线程…

实时数仓,为什么不可代替?

什么是实时数据仓库&#xff1f;它有哪些不可替代之处&#xff1f; 大数据时代中&#xff0c;数据仓库解决了商业智能分析过程中的数据管理问题&#xff0c;但是存在烟囱式、冗余高的弊端 随着商业智能的兴起和数据时代的到来&#xff0c;越来越多的企业开始汇总、整合和分析自…

ArcGIS基础实验操作100例--实验62点、线、面状符号

本实验专栏参考自汤国安教授《地理信息系统基础实验操作100例》一书 实验平台&#xff1a;ArcGIS 10.6 实验数据&#xff1a;请访问实验1&#xff08;传送门&#xff09; 高级编辑篇--实验62 点、线、面状符号 目录 一、实验背景 二、实验数据 三、实验步骤 &#xff08;1&…

C/C++中二级指针传递参数【个人遇到内存值发生改变现象的记录及相关修正方法】

目录 0、前言 1、二级指针传参奇怪现象 2、分析 3、解决方法 0、前言 在c/c中&#xff0c;时常会使用到主调函数通过参数去获取被调函数中的数值情况。针对这种情况&#xff0c;我前面也写过C/C主调函数从被调函数中获取&#xff08;各种类型&#xff09;数据内容方式的梳理…

【ONE·R || 两次作业(一):R基础数据处理】

总言 两次作业汇报&#xff1a;其一。    文章目录总言1、作业一&#xff1a;1.1 、任务一&#xff1a;各项数据建立1.2 、任务二&#xff1a;去除缺失值1.3 、任务三&#xff1a;返回性别为女生&#xff0c;年龄<20的学生及成绩1.4、 任务四&#xff1a;统计性别为女生&a…

【Python百日进阶-数据分析】Day149 - plotly直方图:go.histogram()

文章目录4.2 利用 go.Histogram 的直方图4.2.1 基本直方图4.2.2 归一化直方图4.2.3 水平直方图4.2.4 叠加直方图4.2.5 堆叠直方图4.2.6 风格直方图4.2.7 直方图条形文本4.2.8 累积直方图4.2.9 指定聚合函数4.2.10 自定义分箱4.2.11 在直方图之间共享 bin4.2.12 按类别顺序排序直…

深度学习(一)-环境安装

前言&#xff1a; 最近电脑重装了下系统&#xff0c;然后所有环境啥的都得重新配置一遍&#xff0c;刚好趁着这个时间记录下整个环境的配置过程 注意&#xff1a;本文记录的仅为window系统的配置过程! 一、Anaconda安装及相关配置 Anaconda下载地址&#xff0c;根据需要选择需…

TypeScript 中 Class incorrectly implements interface 错误

当一个类在没有指定接口上定义的所有属性和方法的情况下实现接口时&#xff0c;会发生错误“Class incorrectly implements interface”。 要解决该错误&#xff0c;需要确保定义并键入接口的所有必需属性和方法。 下面是产生上述错误的示例代码 interface Employee {id: num…

Linux学习记录——유 gcc/g++基础知识

文章目录一、程序翻译二、gcc使用1、-o2、预处理-E3、编译-S4、汇编-c5、链接三、库四、库的部分实际操作五、Linux项目自动化构建工具 make/Makefile1、规则一、程序翻译 C语言中&#xff0c;写出代码后&#xff0c;编译器会经过四个阶段才会生成可执行文件。 预处理&#x…

计算数组中元素的加权平均值 numpy.average()

【小白从小学Python、C、Java】【计算机等级考试500强双证书】【Python-数据分析】计算数组中元素的加权平均值numpy.average()[太阳]选择题对于以下python代码最后输出的结果是?import numpy as npa np.array([1, 2, 3, 4])print("【显示】a")print(a)print("…

如何进行Java 单元测试

什么是单元测试 维基百科中是这样描述的&#xff1a;在计算机编程中&#xff0c;单元测试又称为模块测试&#xff0c;是针对程序模块来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。在过程化编程中&#xff0c;一个单元就是单个程序、函数、过程等&#xff1b;…