算法:树状数组详解(c++实现 求解动态数组区间和问题)

news2024/11/13 9:27:54

文章目录

  • 引入
  • 树状数组
  • c++完整代码

引入

什么是树状数组???

解决数据压缩里的累积频率(Cumulative Frequency)的计算问题,现多用于高效计算数列的前缀和, 区间和,这样的问题可以使用树状数组算法来解决。

  • 前面我们讲过求静态数组的子区间和,我们可以利用前缀和来求得
  • 而现在我们引入动态数组的子区间和这一问题,我们可以使用树状数组。

我们现在通过修改数组,往数组中某一个位置增加或减小一个值,从而导致前缀和数组会发生变化,因此我们不可以使用普通前缀和这一思路来做。
考虑一下:我们求静态数组的前缀和时,如果我们使用暴力法,使用两个for循环来枚举子区间之和,从而得到静态数组的区间和,但是这一复杂度会达到O(N^2),因此使用普通的前缀和可以将复杂度达到O(N)。但是当我们在动态数组中修改元素时,每次修改元素会导致原来的前缀和发生变化,因此我们还需要维护前缀和,这样的时间复杂度也会达到O( N^2)。

但是如果我们使用树状数组这一算法,时间复杂度将会降为O(nlog2n),接下来我将详细解释一下树状数组


树状数组

树状数组包含三个基本的功能:

  • 维护前缀和数组:在某个位置增加减少值时,需要调用此函数,来动态维护前缀和
  • 查询前缀和:求某个区间 [l,r]的区间和,即 sum[r]-sum[l-1]
  • lowbit运算:这个概念非常重要,用于得到某个数的二进制的最低位的一,有什么用呢,我们待会再说。

首先定义tree数组表示一个树状数组tree[i]表示第i个位置的区间的和,区间[l,r]的和即为tree[r]-tree[l-1]

  • 首先来看维护前缀和数组:update的函数意思是在数组的 第i个元素加上元素x,x可以是正负,分别代表tree[i]加或者减一个值
//维护
void update(int i,int x)
{
	/*
	i:下标
	x:增量
	维护:在末尾的1上加1
	*/
	while (i <= n)
	{
		tree[i] += x;
		i += lowbit(i);
	}
}
  • 查询区间的和: 求[l,r]的区间和即为 sum[r]-sum[l-1]
//求和
int sum(int i)
{
	/*
	求和(查询):去除末尾的1
	*/
	int num = 0;
	while (i)
	{
		num += tree[i];
		i -= lowbit(i);
	}
	return num;
}
  • lowbit操作:当前一个数字的补码与原数相与得到的便是当前数字二进制的最后一个1。
inline int lowbit(int x)
{
	return ((x) & (-x));
}


接下来详细解释一个这三个函数基本功能:

引用《百度百科》的一张图来解释
在这里插入图片描述

对于查询的过程:每次去掉二进制的最后的1,例如我们查询sum(7):

  • 7的二进制是 111 ,去掉最后的1,得到:110,即十进制为6,则sum(6)
  • 6的二进制是110,去掉最后的1,得到:100,即十进制为4,则sum(4)
  • 4的二进制是100,去掉最后的1,得到:0,为零则结束

所以说查询sum(7)就等于:sum(7) = sum(6)+sum(4),如上图的蓝色表示。


对于维护的过程:每次在二进制数最后的1上加1,例如我们更新update(1,2):

  • 1的二进制是1,在最后一个1上加1得10,十进制为2,所以说tree[2]也会跟着修改
  • 2得二进制是10,在最后一个1上加1得100,十进制为4,所以说tree[4]也会跟着修改
  • 4得二进制是100,在最后一个1上加1得1000,十进制为8,所以说tree[8也会跟着修改

所以说当维护(修改一个位置的元素值)时,我们会自动动态的维护整个树状数组,修改tree[1]+=2 ,tree[2] tree[4] tree[8] … 都会跟着修改。,一直到整个数组的大小N为止。


对于lowbit运算的过程:

lowbit运算可以得到二进制最后一个1的位置,同时也可以根据lowbit确定tree数组的构成

首先我们需要理解tree数组的意思:tree数组本质是前缀和,即tree[i]=a[1]+a[2]+…+a[i]的区间的和。

接下来我们枚举1-7数字的二进制以及lowbit运算的结果,看看lowbit运算是如何影响tree数组的

在这里插入图片描述
首先我们可以了解到:

  • 数字i为奇数时,lowbit始终结果为1,因此我们就可以推断这个1代表tree[i]=a[i] ,代表它只有一个原数组a[i]的值
  • 数字i为偶数时,lowbit结果似乎从数字2,4来看满足:2的时候有两项,4的时候有四项吗,但是并不是这样!!!!!!比如数字6就只有2项。当i为偶数的时候,实际上看的是二进制的最后一位数字1及其之后所表示的低位的值,例如 10就是2;100就是4,110就是2(10);11110还是2(最后一位1与后面的0);11000就是8

因此:tree数组的每一项的值:

  1. 当数字为1时,lowbit运算为1,所以tree[1]=a[1] (奇数肯定只有一项)
  2. 当数字为2时,lowbit运算为10,所以tree[2]=a[2]+a[1],有两项
  3. 当数字为3时,lowbit运算为1,所以tree[3]=a[3]
  4. 当数字为4时,lowbit运算为100,所以tree[4]=a[4]+a[3]+a[2]+a[1] ,有四项
  5. 当数字为5时,lowbit运算为1,所以tree[5]=a[5]
  6. 当数字为6时,lowbit运算为10,所以tree[6]=a[6]+a[5] ,有两项
  7. 当数字为7时,lowbit运算为1,所以tree[7]=a[7]
  8. 当数字为8时,lowbit运算为1000,所以tree[8]=a[8]+ … a[1],有八项

即我们可以得到这样一张图:

请仔细理解这张图代表的意义!!!!!!!!!
在这里插入图片描述
对于tree[4]=a[1]+a[2]+a[3]+a[4],其实就是tree[4]=tree[3]+a[4],对于其他也是同理。


c++完整代码

#include <bits/stdc++.h>
using namespace std;

/*
树状数组:常用于对动态区间的查询操作,即求动态数组求前缀和的操作
三大基本功能:
1. 维护 update
2. 求和 sum
3. lowbit运算
*/
const int N = 10005;
int tree[N]{ 0 };
int n;		//当前tree数组的最大元素个数
inline int lowbit(int x)
{
	return ((x) & (-x));
}

//维护
void update(int i,int x)
{
	/*
	i:下标
	x:增量
	维护:在末尾的1上加1
	*/
	while (i <= n)
	{
		tree[i] += x;
		i += lowbit(i);
	}
}

//求和
int sum(int i)
{
	/*
	求和(查询):去除末尾的1
	*/
	int num = 0;
	while (i)
	{
		num += tree[i];
		i -= lowbit(i);
	}
	return num;
}
int main()
{
	n = 5;
	for (int i = 1; i <= 5; i++)
	{
		update(i, 3);
	}
	// 3 6 9 12 15
	cout << "S[2,4]: " << sum(4) - sum(1) << endl;

	//更新数组元素
	// 3 6 15 18 21
	update(3, 6);

	cout << "S[2,4]: " << sum(4) - sum(1) << endl;
	return 0;
}

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

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

相关文章

Day8 spring 注解开发

1 前言使用注解类代替xml配置&#xff0c;使用注解代理xml中的标签2 标签2.1 用于Bean(类)上的常见注解2.1.1 Component步骤&#xff1a;在xml中配置包扫描&#xff0c;使得spring知道在哪些类上加上Component在特定类上加上Component测试/*** author : msf* date : 2023/1/24*…

#I. 哆啦A梦的时光机(bfs经典习题)

题目说明有一天&#xff0c;大雄和他的伙伴们想穿越时空进行探险&#xff0c;可是时光机却出了一点故障&#xff0c;只能进行有限的时空穿越操作。大雄他们需要从现在出发&#xff0c;到达一个目标时间点进行探险&#xff0c;结束后再返回到现在&#xff0c;他们希望尽可能减少…

Linux——网络编程概述

Q&#xff1a;为什么要网络编程&#xff1f;A&#xff1a;进程间的通信是依赖于Linux内核的&#xff0c;只能适用于单机&#xff0c;而要实现联机&#xff0c;就要用网络编程&#xff0c;网络编程是面向多台设备间的通信网络编程两个重要概念&#xff1a;IP地址和端口&#xff…

微服务拆分之道

背景 微服务在最近几年大行其道&#xff0c;很多公司的研发人员都在考虑微服务架构&#xff0c;同时&#xff0c;随着 Docker 容器技术和自动化运维等相关技术发展&#xff0c;微服务变得更容易管理&#xff0c;这给了微服务架构良好的发展机会。 在做微服务的路上&#xff0…

JavaWeb | JDBC相关API详解 1(含源码阅读)

本专栏主要是记录学习完JavaSE后学习JavaWeb部分的一些知识点总结以及遇到的一些问题等&#xff0c;如果刚开始学习Java的小伙伴可以点击下方连接查看专栏 本专栏地址&#xff1a;&#x1f525;JDBC Java入门篇&#xff1a; &#x1f525;Java基础学习篇 Java进阶学习篇&#x…

5、常量与变量

目录 1.数值型变量 &#xff08;1&#xff09;整型常量 &#xff08;2&#xff09;实型常量 2.字符型常量 (1)字符常量 (2) 字符串常量 3. ASCII表 4. 转义符号 5.符号常量 6. 整型变量 &#xff08;1&#xff09;有符号基本整型 &#xff08;2&#xff09;无符号基…

JavaScript Break 和 Continue 语句

文章目录JavaScript Break 和 Continue 语句Break 语句Continue 语句JavaScript 标签笔记列表JavaScript Break 和 Continue 语句 break 语句用于跳出循环。 continue 用于跳过循环中的一个迭代。 Break 语句 我们已经在本教程之前的章节中见到过 break 语句。它用于跳出 swi…

final的一个重要用途-宏变量和未初始化问题

/*** author 张家琛* version 1.0* date 2023/1/24 20:23*/ public class FinalDemo {public static void main(String[] args) {final var a5;System.out.println(a);} } 对于上面的程序来说&#xff0c;变量a其实根本就不存在&#xff0c;这段代码本质上执行的是转换成的&am…

MyBatis知识点笔记

目录 mybatis mapper-locations的作用&#xff1f; mybatis configuration log-impl 作用&#xff1f; resultType和resultMap的区别&#xff1f; 参数 useGeneratedKeys &#xff0c;keyColumn&#xff0c;keyProperty作用和用法 取值方式#和$区别 动态标签有哪些 MyBat…

字节青训前端笔记 | Vite 入门

本节课&#xff0c;讲师将前置介绍有关前端工程基本知识&#xff0c;同时讲解前端构建工具及解决问题&#xff0c;进而引出作为前端构建工具的 Vite 是什么及其在开发过程中的应用。 为什么使用构建工具 前端是有一系列资源组成的&#xff0c;js 代码&#xff0c;css样式&…

ESP32 Micropython编程(Thonny)04---- 人体感应模块

本人持续分享更多关于嵌入式和单片机的知识&#xff0c;如果大家喜欢&#xff0c;别忘点个赞加个关注哦&#xff0c;让我们一起共同进步 最近使用到人体感应模块HC-SR501&#xff0c;这个模块使用十分简单&#xff0c;看下面代码就可以看出了&#xff0c;而且应用广泛&#xff…

Java 优先级队列(堆)

目录1. 优先级队列&#xff08;堆&#xff09;的概念2. 建立大根堆&#xff08;向下调整算法&#xff09;3. 堆插入元素&#xff08;向上调整算法&#xff09;4. 堆删除元素&#xff08;向下调整算法&#xff09;5. 优先级队列PriorityQueue的特性6. 优先级队列PriorityQueue的…

单向环形链表和约瑟夫问题

单向环形链表和约瑟夫问题 Josephu 问题为&#xff1a;设编号为1&#xff0c;2&#xff0c;… n的n个人围坐一圈&#xff0c;约定编号为k&#xff08;1<k<n&#xff09;的人从1开始报数&#xff0c;数到m 的那个人出列&#xff0c;它的下一位又从1开始报数&#xff0c;数…

ubuntu压缩、归档 tar zip gzip的介绍

不同文件需要使用不同的工具进行压缩和解压&#xff0c;可以使用file文件查看是什么类型的文件 file 文件名如图&#xff0c;在第二行看到这个文件是gzip类型的。 文件&#xff1a;gzip 工具&#xff1a;gunzip gunzip是 gnu unzip的缩写。用来解压gzip压缩包。 解压命令 …

linux 内存管理

0.前言 1.了解Linux内存的管理机制(分段分页) 2.了解虚拟内存和物理内存的映射方式 3.了解操作系统内存与磁盘的交互&#xff08;分页机制---》缺页重读机制&#xff0c;用时拷贝机制) 4.应用程序如何高效使用内存和高级程序的设计方法 1.linux物理使用情况 内核区&#x…

动态站点地图提交百度收录

站点地图&#xff08;sitemap&#xff09;是一个网站的结构化数据&#xff0c;搜索引擎可以通过站点地图迅速了解一个网站的内容&#xff0c;加快搜索引擎收录。 一般来说&#xff0c;站点地图是以.xml结尾的静态化文件&#xff0c;例如个人博客和生化环材网的站点地图链接分别…

【Mysql】事务的四大特性(ACID)

【Mysql】事务的四大特性(ACID) 文章目录【Mysql】事务的四大特性(ACID)1. 概述1.1 并发事务问题1.1.1 脏读1.1.2 不可重复读1.1.3 幻读1.2 事务隔离级别1. 概述 事务的四大特性&#xff1a; 原子性&#xff08;Atomicity&#xff09;&#xff1a;事务是不可分割的最小操作单…

ROS学习寄录2

1 ROS核心概念 1.1 节点&#xff08;Node&#xff09; &#xff08;1&#xff09;执行具体任务的进程、独立运行的可执行文件 &#xff08;2&#xff09;不同节点可以使用不同的语言&#xff0c;可分布式运行在不同的主机 &#xff08;3&#xff09;节点在系统中的名称必须…

MODBUS总线的学习笔记

MODBUS学习记录 下面所有资料均copy于安富莱电子和博客中&#xff0c;仅作为个人学习笔记记录&#xff0c;写的不好请见谅。 1.modbus简介介绍 Modbus 是由 Modicon&#xff08;现为施耐德电气公司的一个品牌&#xff09;在 1979 年发明的&#xff0c;是全球第一个真正 用于…

PowerShell 学习笔记:操作XML文件

XML文件是有一定格式要求的文本文件。百度百科可扩展标记语言 (Extensible Markup Language, XML) &#xff0c;标准通用标记语言的子集&#xff0c;可以用来标记数据、定义数据类型&#xff0c;是一种允许用户对自己的标记语言进行定义的源语言。 XML是标准通用标记语言 可扩展…