树状数组(详细分析+应用),看不懂打死我!

数据结构之树状数组 ——详解 树状数组是一种维护前缀和、区间和的数据结构。它在原数组上添加索引,从而高效维护数据。神奇而小巧的数据结构,树状数组的下标必须从1开始!无论是他的修改还是查询,时间复杂度永远都是 logn,适合用于做区间和,前缀和,单点修改比前缀和标准做法的修改快一些。 阅读详情
AI摘要

树状数组是一种简化线段树的数据结构,主要用于区间修改和查询操作。它删除了部分二叉树节点以提高效率。树状数组支持单点修改和区间查询,具有O(logN)的时间复杂度,但扩展性较弱。其主要优点在于实现简单,常数较小。通过lowbit运算,可以快速更新和查询。在解决区间问题时,树状数组提供了一种高效的选择,但在某些复杂场景下可能需要线段树。本文通过实例介绍了树状数组的基本操作,并提供了相关题目以供练习。

树状数组介绍

在学习一个算法之前一定要清楚它能干嘛,能解决什么样的问题,对你解题是否有帮助,然后才去学习它!

那么接下来看如下几个问题

什么是树状数组

顾名思义就是一个结构为树形结构的数组,于二叉树的结构类似但又不同,它是在二叉树的结构上删除了一些中间节点,来看两幅图就明白了.

1.这是二叉树的结构
在这里插入图片描述
2.这是树状数组的结构
在这里插入图片描述
不难发现,树状数组相比于二叉树删除了一些节点,但是为什么要删除呢?这就和树状数组的一些性质(lowbit)有关了,不懂没关系,继续往下看.

树状数组可以解决什么问题呢?

可以解决大部分区间上面的修改以及查询的问题,例如1.单点修改,单点查询,2.区间修改,单点查询,3.区间查询,区间修改,换言之,线段树能解决的问题,树状数组大部分也可以,但是并不一定都能解决,因为线段树的扩展性比树状数组要强.

树状数组和线段树的区别在哪?

有人会问了既然线段树的问题能够用树状数组解决而且线段树还比树状数组扩展性强,那为什么不直接用线段树呢?问的很好,树状数组的作用就是为了简化线段树,举个例子:一个问题可以用线段树解决写代码半个小时,但是用树状数组只需要10分钟,那么你会选择哪一个算法呢?没错,基于某些简单的问题,我们没必要用到功能性强但实现复杂的线段树(杀鸡焉用宰牛刀).

树状数组的优点

优点:修改和查询操作复杂度于线段树一样都是logN,但是常数比线段树小,并且实现比线段树简单

缺点:扩展性弱,线段树能解决的问题,树状数组不一定能解决.

树状数组讲解

前置知识—lowbit(x)运算

如何计算一个非负整数n在二进制下的最低为1及其后面的0构成的数?
例如: 44 = ( 101100 ) 2 44=(101100)_2 44=(101100)2​,最低为1和后面的0构成的数是 ( 100 ) 2 = 4 (100)_2=4 (100)2​=4
所以 l o w b i t ( 44 ) = l o w b i t ( ( 101100 ) 2 ) = ( 100 ) 2 = 4 lowbit(44)=lowbit((101100)_2)=(100)_2=4 lowbit(44)=lowbit((101100)2​)=(100)2​=4,那么lowbit运算时怎么实现的呢?

44的二进制=(101100),我们对44的二进制数取反+1,也即~44+1,得到-44

-44的二进制=(010100),然后我们把44和-44的二进制进行按位与运算,也即按位&得到,二进制000100,也就是十进制的4

所以lowbit(x) = x&(-x)

问题引入

在这里插入图片描述
显然,我们一开始会想到暴力的朴素做法,单点修改操作时间复杂度O(1),区间求和,暴力遍历区间每一个数再相加时间复杂度O(n),如果区间求和查询的次数为n次,那么中的时间复杂度为O( n 2 n^2 n2),对于大数据的题来说肯定会T,此时如果用树状数组的话复杂度可以讲到O(nlogn).

树状数组结构分析

接下来分析树状数组的原理
在这里插入图片描述
上面时树状数组的结构图,t[x]保存以x为根的子数中叶子节点值的和,原数组为a[]
那么原数组前4项的和t[4]=t[2]+t[3]+a[4]=t[1]+a[2]+t[3]+a[4]=a[1]+a[2]+a[3]+a[4],看似没有什么特点,别着急往下看

在这里插入图片描述
我们通过观察节点的二进制数,进一步发现,树状数组中节点x的父节点为x+lowbit(x),例如t[2]的父节点为t[4]=t[2+lowbit(2)]

单点修改,区间查询

所以我们在单点修改的同时,更新父节点就变得尤为简单,,例如我们对a[1]+k,那么祖先节点t[1],t[2],t[4],t[8]都需要+k更新(因为t[]表示前缀和),此时我们就可以用lowbit操作实现.
代码

int add_dandian(int x,int k)
{
	for(int i=x;i<=n;i+=lowbit(i))
	t[i]+=k;
}

那么单点修改实现了,如何实现区间查询呢?
例如:我们需要查询前7项的区间和sum[7]
在这里插入图片描述
通过图中不难看出,sum[7]=t[7]+t[6]+t[4] ,我们进一步发现,6=7-lowbit(7),4=6-lowbit(6),所以我们可以通过不断的-lowbit操作来实现求和
代码

int ask(x){
	int sum = 0;
	for(int i=x;i;i-=lowbit(i)){
		sum+=t[i];
	}
	return sum;
}

这只能求区间 [ 1 , x ] 的区间和,那么如何求 [ L , R ] 的区间和呢? [1,x]的区间和,那么如何求[L,R]的区间和呢? [1,x]的区间和,那么如何求[L,R]的区间和呢?,这时候利用前缀和相减的性质就可以了, [ L , R ] = [ 1 , R ] − [ 1 , L − 1 ] [L,R]=[1,R]-[1,L-1] [L,R]=[1,R]−[1,L−1]
代码

int search(int L,int R)
{
	int ans = 0;
	for(int i=L-1;i;i-=lowbit(i))
	ans-=c[i];
	for(int i=R;i;i-=lowbit(i))
	ans+=c[i];
	return 0;
}

区间修改,单点查询

对于这一类操作,我们需要构造出原数组的差分数组c,然后用树状数组维护c数组即可

对于区间修改的话,我们只需要对差分数组进行操作即可,例如对区间[L,R]+k,那么我们只需要更新差分数组add(L,k),add(R+1,-k),这是差分数组的性质.
代码

int update(int pos,int k)//pos表示修改点的位置,K表示修改的值也即+K操作
{
	for(int i=pos;i<=n;i+=lowbit(i))
	c[i]+=k;
	return 0;
}
update(L,k);
update(R+1,-k);

对于单点查询操作,求出c数组的前缀和即可,因为a[x]=差分数组c[1]+c[2]+…+c[x]的前缀和,这是差分数组的性质之一.
代码

ll ask(int pos)//返回区间pos到1的总和
{
	ll ans=0;
	for(int i=pos;i;i-=lowbit(i)) ans+=c[i];
	return ans;
} 

区间修改,区间查询

这一类操作使用树状数组就显得及其复杂,这时候我们建议使用扩展性更强的线段树来解决,在此就不进行树状数组的讲解了.

树状数组题目练习

下面2到题都是模板题,不需要经行讲解,学会了上面的树状数组知识就可以AC

树状数组1
AC代码

#include<iostream>
#define lowbit(x) (x&(-x))
typedef long long ll; 
using namespace std;
int c[2000006];
int n,m;
ll ans;
int add_dandian(int x,int k)
{
	for(int i=x;i<=n;i+=lowbit(i))
	c[i]+=k;
}
int search(int begin,int end)
{
	for(int i=end;i;i-=lowbit(i))
	ans+=c[i];
	for(int i=begin-1;i;i-=lowbit(i))
	ans-=c[i];
	return 0;
}
int main()
{
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		int number;
		scanf("%d",&number);
		add_dandian(i,number);
	}
	for(int i=1;i<=m;i++)
	{
		int choice,x,y;
		scanf("%d %d %d",&choice,&x,&y);
		if(choice==1) add_dandian(x,y);
		else
		{
			ans=0;
			search(x,y);
			printf("%lld\n",ans);
		}
	}
	return 0;
}

树状数组2

#include<iostream>
#include<cstring>
#define lowbit(x) (x&(-x)) 
using namespace std;
typedef long long ll;
const int Maxn=1e6+5;
int a[500005];
int d[Maxn]={0};//d[i]的值,d[i]表示第i和i-1个数的差值
ll c[Maxn]; 
int n,m;
int update(int pos,int k)//pos表示修改点的位置,K表示修改的值也即+K操作
{
	for(int i=pos;i<=n;i+=lowbit(i))
	c[i]+=k;
	return 0;
}
ll ask_qujian(int pos)//返回区间pos到1的总和
{
	ll ans=0;
	for(int i=pos;i;i-=lowbit(i)) ans+=c[i];
	return ans;
} 
int main()
{
	memset(c,0,sizeof(c));
	scanf("%d %d",&n,&m);
	a[0]=0;
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		d[i]=a[i]-a[i-1];
		update(i,d[i]);
	}
	for(int i=1;i<=m;i++)
	{
		int choice,x,y,k;
		scanf("%d",&choice);
		if(choice==1)
		{
			scanf("%d %d %d",&x,&y,&k);
			update(x,k);
			update(y+1,-k);
		}
		else
		{
			scanf("%d",&x);
			printf("%lld\n",ask_qujian(x));
		}
	}
	return 0;
}

树状数组求逆序对
分析:对于原序列,比当前位置数大的数先出现在序列中,就会构成逆序对,例如:5 3 2 1,5,3,2比1先出现且都比1大,那么此时就构成了3个逆序对数,那么我们可以对已经出现的数字进行标记,枚举序列中每一个位置的数,统计有多少比它大的数字已经出现,然后累加进答案即可,这就是一个单点修改+区间查询的操作,树状数组实现,不过需要注意的是这道题数字很大,需要离散化存储.

注意:这道题自定义排序参数cmp的实现,不能单纯的a.val<b.val,如果相等的话也要保证位置不变,不然贡献会增多,想想为什么?.
AC代码

#include<bits/stdc++.h>
using namespace std;
const int Maxn = 5e5+10;
int t[Maxn]={0};//树状数组 
typedef struct node{
   int val,ind;
}Node;
Node stu[Maxn];
int Rank[Maxn];
typedef long long ll;
int n; 
int lowbit(int x){return x&(-x);}
/*单点修改*/
void add(int pos){
	for(int i=pos;i<=n;i+=lowbit(i)) t[i]+=1;
}
/*区间求和*/
int ask(int pos){
	int ans = 0; 
	for(int i=pos;i;i-=lowbit(i)) ans+=t[i];
	return ans;
} 
/*不能单纯的a.val<b.val,如果相等的话也要保证位置不变,不然贡献会增多*/
int cmp(Node a,Node b){
	if(a.val==b.val)
	return a.ind<b.ind;
	
	return a.val<b.val;
}
int main()
{
	ll ans = 0;
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>stu[i].val;
		stu[i].ind=i;
	}
	sort(stu+1,stu+n+1,cmp);
	/*离散化操作*/
	for(int i=1;i<=n;i++){
		Rank[stu[i].ind] = i;
	} 
	for(int i=1;i<=n;i++){
		int pos = Rank[i];

		ans+=ask(n)-ask(pos);//digit+1~n中有多少数字已经出现就贡献多少逆序对数,累加到答案 
			add(pos);//单点修改
	}
	cout<<ans;
	return 0;
}
树形数组及应用 树状数组是一个查询和修改复杂度都为的数据结构。主要用于:数组的单点修改、区间求和。在使用前缀和求区间和的算法中,如果可以做到在的时间复杂度内查询任意的区间和,但是如果要修改其中一个点的值,那么需要修改一遍前缀和数组,修改的时间复杂度是。 阅读详情

相关推荐

IEC103协议实战指南:报文解析与传输优化策略

本文深入解析IEC103协议在电力自动化系统中的核心应用,重点拆解其固定帧与可变帧两种报文格式,并详细阐述了主从问答与非平衡传输机制。文章结合实战经验,针对报文解析中的常见问题(如字节序、超时重发)提供了解决方案,并探讨了传输层优化策略,旨在帮助工程师实现更可靠、高效的通信链路。

r2s3t4的博客 541

【高级数据结构】树状数组

树状数组(BinaryIndexedTree,BIT)(Binary Indexed Tree,BIT)(BinaryIndexedTree,BIT)是利用数的二进制特征进行检索的一种树状的结构。如何利用二分的思想高效地求前缀和? 如图4.74.74.7所示, 以AAA =[a1,=[a_1,=[a1​,a2a_2a2​ ,a3a_3a3​…a8]a_8]a8​]为例,将二叉树的结构画成树状。这幅图是树状数组的核心,理解了它,就能明白树状数组的一切操作。图4.74.74.7圆圈中标记有数字的节点,存储的是称

Antonio915的博客 6174

WPS Office 2026免费安装指南:AI办公与安全使用全解析

办公软件作为数字化办公的核心工具,其基本原理是通过文档处理、数据计算和演示展示等功能模块满足用户日常办公需求。随着人工智能技术的发展,现代办公软件逐步集成AI能力,通过自然语言处理和数据分析算法提升工作效率。在技术价值层面,优秀的办公软件不仅需要保持格式兼容性和跨平台同步能力,更要注重用户数据安全防护。从应用场景来看,无论是个人文档编辑、企业报表分析还是团队协作演示,办公软件都发挥着关键作用。WPS Office 2026作为国产办公软件代表,深度融合AI助手功能,支持智能写作和数据分析,同时通过官方免费策

weixin_30599769的博客 704

树状数组详解

文章目录前言一、树状数组概括二、树状数组的应用1.单点修改+区间查询总结 前言 树状数组或二叉索引树(Binary Indexed Tree),又以其发明者命名为 Fenwick 树。其初衷是解决数据压缩里的累积频率的计算问题,现多用于高效计算数列的前缀和、区间和。它可以以 O(logn) 的时间得到任意前缀和。并同时支持在 O(logn) 时间内支持动态单点值的修改。空间复杂度 O(n)。 一、树状数组概括 树状数组是一个查询和修改复杂度都为log(n)的数据结构。主要用于数组的单点修改&

萧何山的博客 2万+

ArcMap 10.7/10.8启动加载界面后闪退?可能是这个隐藏的Normal.mxt模板文件在搞鬼

本文深入分析了ArcMap 10.7/10.8启动加载界面后闪退的问题,指出Normal.mxt模板文件损坏是常见原因。提供了从定位、删除模板文件到系统级修复的完整解决方案,帮助GIS工程师快速恢复工作流程。

weixin_28523493的博客 329

Android 开发实战:从零到一集成 espeak-ng 实现中文离线 TTS(无需账号开箱即用)

本文详细介绍了如何在 Android 项目中集成 espeak-ng,实现中文离线语音播报。通过下载 so 库、编写 Java 接口、封装管理类,开发者可快速构建无需账号的 TTS 功能。文章覆盖从零到一的开发步骤、调试技巧及性能优化,适合中高级 Android 开发者参考。

全栈 3316

树状数组(超详细)

树状数组千字精华讲解

Jiguoyuanyic的博客 4223

AI Agent开发第84课-paddleocr-vl全网唯一最全本地化布署教程

本文详解 PaddleOCR-VL 本地化布署全过程,指出为何在真实生产环境中 PaddleOCR-VL 更稳定、更易部署、更贴近工程落地。同时还会详细比较与 DeepSeek-OCR 的核心差异,为全网独家介绍paddleocr-vl的本地部署实战指南。

打造全国最全的AI Agent开发知识领域的博客 5966

数据结构--树状数组

树状数组是一种用于高效处理动态数组中前缀和查询的数据结构。它能够在OlognO(logn)O(logn)O(1)更新,O(n)前缀和O(n)更新,O(1)前缀和如果问题同时要大量更新和求前缀和,上述两种数据结构均会寄掉树状数组则采取折中思路,把整体O(logn)

mathematicians的博客 2888

一、OpenSM 架构部署及原理详解

OpenSM 是 InfiniBand 网络的核心组件,通过自动化的拓扑发现、路由计算和状态维护,确保网络的高效运行。部署时需关注配置文件参数、路由算法选择和高可用性设计,调试时可借助日志和诊断工具快速定位问题。

jundao1997的专栏 1128

ESP32S3 TLS 握手WIFI可用但是在蜂窝热点下卡死:排查方法与根因

设备卡在等。

摸鱼专家 85

树状数组数据结构

1.树状数组概念 树状数组简单来说就是仅通过一个数组来描述一棵树或一个森林。数组中每个元素只存储着树中对应节点存储的信息,不存储描述树的层次结构的信息。 2.普通树的存储 对于一棵普通的树,在《数据结构》严蔚敏版中提到可以使用如下方法进行存储: 2.1 父节点表示法 用一个数组中存储节点信息,节点信息包括节点存储数据和父节点编号。 struct Node { int data; // 节点存储的数据 int parent; // 节点对应的父节点的编号 } struct Tree {

qq_37464878的博客 3007

大模型部署指南:SGLang 框架

本文介绍使用 SGLang 框架部署大模型

举世誉之而不加劝,举世非之而不加沮,定乎内外之分,辩乎荣辱之境,斯已矣。 1610

博客摘录「 树状数组(详细分析+应用),看不懂打死我!」2025年6月22日

修改和查询操作复杂度于线段树一样都是logN。

wlxwlx5的博客 93

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

树状数组学习笔记(Python)

拜读了大佬的讲解博文,写一篇Python版的笔记巩固消化,附带蓝桥杯历年真题作为例题演示。

m0_72379992的博客 1214

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

树状数组1,ACM暑期培训

注意:1、树状数组一般的数组一般从下标1开始赋值0作为一个边界;2、树状数组基本用法是:区间修改,区间查询,其他用法均在此基础上拓展。

Landing_on_Mars的博客 242

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

算法学习笔记:树状数组(一维、二维)

本文介绍了一维和二维树状数组的:单点修改、区间查询;区间修改、单点查询;区间修改、区间查询。详细介绍了树状数组的原理,全面覆盖了树状数组的各个知识点

X979477920的博客 600

数据结构:树状数组

老规矩,推荐一篇原理讲解清晰的博客!

y2706586063的博客 358

树状数组核心模板与实战

关于树状数组的博客已经很多了,这里提供三道最基础的模板题以及一道练习题(2024沈阳区域赛铜牌题)

python_yyd的博客 376

树状数组POJ-2299

树状数组是为了解决一些单点修改 区间查询的问题 如果加上差分数组可以用来解决区间修改和单点查询问题 由于编程简单可以用来替代一些线段树的情况 但不是绝对的。这一类操作使用树状数组就显得及其复杂,这时候我们建议使用扩展性更强的线段树来解决,在此就不进行树状数组的讲解了.只能相邻的交换 本题转换为消除所有的逆序对需要多少次 一次只能消除一对 求逆序对总数。本题的关键是逆序对:i>j a[i]<a[j]为一个逆序对。利用树状数组实现同时离散化。也就是poj-2299。

m0_73596392的博客 100

【真香笔记இ௰இ】线段树和树状数组

文章开头第一句:回家旅游一时爽,______________ 。 Day4-线段树和树状数组(简单) Day5-线段树和树状数组(进阶) 树状数组 是什么:是数组,原理图: 为什么:适用于单点更新+区间查询 怎么弄: 两个数组:A是原数据数组,C是树状数组 两个函数 线段树 是什么:是树,原理图: 为什么:使用条件:满足区间加法。(即区间[L,R]的答案可以由[L,M]和[M+1,R]得到) ...

crane_起重菜鸡的博客 343

leetcode | go | 第307题 | 区域和检索-数组可修改

区域和检索-数组可修改

iCling的博客 162

P3688 [ZJOI2017]树状数组

Scarlet:为什么你爆零那么熟练? wamach:QAQ

wamach的博客 921

poj3067 Japan 树状数组+逆序对

日本有N个城市在东边,从北至南编号为1 2 3,,,N,M个城市在西边,从北至南编号为1 2 ,,,,M,K条高速公路将被建造 高速公路的一端在西边,一端在东边 输入有多组样例, 每组样例第一行为 n m k 接下来有k行,分别为高速公路的起点和端点 求高速公路的交点有多少个,不包括以城市为相交点 打死我也想不到用树状数

Little_boy_z的博客 383

HDU 2492 pingpang(树状数组)

问题描述:给出一列数Ai,任选三个上升或下降的数为一组,问可以有这样的多少组。  数据范围:0  分析:我觉得。。打死我也想不出这个用树状数组。。            问题转化成 求每个数左右两边比它小的数。因为扫一遍就已经n了。。再累计比它小的数,说明是遍扫描边更新。树状数组求和和改变的操作复杂度为O(log ai)。扫描到每个数时,我们可以计算sum(a[i]-1),再add(a[i]

Sundance 271

poj 2155 Matrix 二维树状数组

题意 就是给你一个 矩形 然后 执行两种操作 1 对任意一个给出的矩形的所有单元格进行取反操作;2 询问某一点是1 还是0; 方法 打死都想不到可以用树状数组来做;而且是二维的树状数组来做(听说这个题目是楼天成出的);首先需要了解翻转; 有一篇博客写得很好 http://blog.youkuaiyun.com/zxy_snow/article/details/626413...

weixin_30315435的博客 66
上一篇: maven的安装详解
下一篇: dfs序+树链剖分,超详细讲解+原理分析+模板(看不懂来打我)
鲜果维他命
博客等级 码龄7年 205粉丝 92原创
评论 101
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值