扫清盲点:带你学习 树状数组 这种数据结构

news2024/11/15 11:21:02

什么是树状数组

树状数组是一种用于维护数列前缀和的数据结构,它可以在 O(logn) 的时间复杂度内修改单个元素的值,以及查询某个区间的元素和。

树状数组的特点是什么?

  • 树状数组的特点其实就是,在单点修改 ,和区间查询,它所需要写的代码量少,与时间复杂度低而闻名。
  • 但是树状数组需要的空间复杂度很高,原因是,假如我们的查询的数字x的大小有100000这么大,那么这个树状数组的节点个数就得有100001个这么多。

讲到这里,大家对树状数组大致上有了那么一丢丢了解。我们再来结合这张图,再来感受一下树状数组的工作原理。

树状数组的工作原理

在这里插入图片描述

  • 最下面的八个方块代表原始数据数组 a。上面参差不齐的方块(与最上面的八个方块是同一个数组)代表数组 a 的上级——c 数组。

  • c 数组就是用来储存原始数组 a 某段区间的和的,也就是说,这些区间的信息是已知的,我们的目标就是把查询前缀拆成这些小区间。

  • 例如,从图中可以看出:

    • c_2 管辖的是 a[1 … 2];
    • c_4 管辖的是 a[1 … 4];
    • c_6 管辖的是 a[5 … 6];
    • c_8 管辖的是 a[1 … 8];
    • 剩下的 c[x] 管辖的都是 a[x] 自己(可以看做 a[x … x] 的长度为 1 的小区间)。

我们可以把a1到a8理解为一个有序的递增数列。它们的值也就是从1到8。

c1到c8,其实就是这个这个数列的树状数组

管辖区间

不难发现,c[x] 管辖的一定是一段右边界是 x 的区间总信息。我们先不关心左边界,先来感受一下树状数组是如何查询的。

举例:计算 a[1…… 7] 的和。

过程:从 c7开始往前跳,发现 c7}只管辖 a7这个元素;然后找 c6,发现 c6管辖的是 a[5 … 6],然后跳到 c4,发现 c4 管辖的是 a[1 … 4] 这些元素,然后再试图跳到 c0,但事实上 c0 不存在,不跳了。

那么问题来了,c[x](x >= 1) 管辖的区间到底往左延伸多少?也就是说,区间长度是多少?

树状数组中,规定 c[x] 管辖的区间长度为 2^k,其中:

  • 设二进制最低位为第 0 位,则 k 恰好为 x 二进制表示中,最低位的 1 所在的二进制位数(最右边的第一个1所在的二进制位);

  • 2^k(c[x] 的管辖区间长度)恰好为 x 二进制表示中,最低位的 1 以及后面所有 0 组成的数。

  • 举个例子,c88 管辖的是哪个区间?

    • 因为 88 = 01011000,其二进制最低位的 1 以及后面的 0 组成的二进制是 1000,即 8,所以 c88管辖 8 个 a 数组中的元素。

    • 因此,c88 代表 a[81 … 88] 的区间信息。

  • 我们记 x 二进制最低位 1 以及后面的 0 组成的数为 lowbit(x),那么 c[x] 管辖的区间就是 [x - lowbit(x) + 1, x]。

  • 这里注意lowbit 指的不是最低位 1 所在的位数 k,而是这个 1 和后面所有 0 组成的 2^k。

那么问题又来了。怎么计算 lowbit?

  • 根据位运算知识,可以得到 lowbit(x) = x & -x。
  • 这一点,你可以参考原码,反码,补码的知识去看一下为什么是这样。
  • 扫除盲点:教你清晰理解什么是计算机中的原码,反码,补码

代码实现:

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

区间查询

接下来我们来看树状数组具体的操作实现,先来看区间查询。

我们可以写出查询 a[1 … x] 的过程:

  • 从 c[x] 开始往前跳,有 c[x] 管辖 a[x - lowbit(x) + 1 … x];
  • 令 x - lowbit(x),如果 x = 0 说明已经跳到尽头了,终止循环;否则回到第一步。将跳到的 c 合并。
  • 实现时,我们不一定要先把 c 都跳出来然后一起合并,可以边跳边合并。

比如我们要维护的信息是和,直接令初始 ans = 0,然后每跳到一个 c[x] 就 ans = ans + c[x],最终 ans 就是所有合并的结果。

代码实现:

int getsum(int x) {  // a[1]..a[x]的和
  int ans = 0;
  while (x > 0) {
    ans = ans + c[x];
    x = x - lowbit(x);
  }
  return ans;
}

树状数组与其树形态的性质

在讲解单点修改之前,先讲解树状数组的一些基本性质,以及其树形态来源,这有助于更好理解树状数组的单点修改。

  • 性质 1:对于 x <= y,要么有 c[x] 和 c[y] 不交,要么有 c[x] 包含于c[y]。

  • 性质 2:在 c[x] 真包含于c[x +lowbit(x)]。

  • 性质 3:对于任意 \x < y < x + lowbit(x),有 c[x] 和c[y] 不交。

我们约定:

  • l(x) = x - lowbit(x) + 1。即,l(x) 是 c[x] 管辖范围的左端点。
  • 下面c[x] 和 c[y] 不交指 c[x] 的管辖范围和 c[y] 的管辖范围不相交,即 [l(x), x] 和 [l(y), y] 不相交。c[x] 包含于 c[y]等表述同理。

有了这三条性质的铺垫,我们接下来看树状数组的树形态(请忽略 a 向 c 的连边)。

在这里插入图片描述

事实上,树状数组的树形态是 x 向 x + lowbit(x) 连边得到的图,其中 x + lowbit(x) 是 x 的父亲。

注意,在考虑树状数组的树形态时,我们不考虑树状数组大小的影响,即我们认为这是一棵无限大的树,方便分析。实际实现时,我们只需用到 x <= n 的 c[x],其中 n 是原数组长度。

单点修改

现在来考虑如何单点修改 a[x]。

  • 我们的目标是快速正确地维护 c 数组。为保证效率,我们只需遍历并修改管辖了 a[x] 的所有 c[y],因为其他的 c 显然没有发生变化

  • 管辖 a[x] 的 c[y] 一定包含 c[x](根据性质 1),所以 y 在树状数组树形态上是 x 的祖先。因此我们从 x 开始不断跳父亲,直到跳得超过了原数组长度为止。

  • 设 n 表示 a 的大小,不难写出单点修改 a[x] 的过程:

    • 初始令 x’ = x。
    • 修改 c[x’]。
    • 令 x’ = lowbit(x’),如果 x’ > n 说明已经跳到尽头了,终止循环;否则回到第二步。

区间信息和单点修改的种类,共同决定 c[x’] 的修改方式。下面给几个例子:

  • 若 c[x’] 维护区间和,修改种类是将 a[x] 加上 p,则修改方式则是将所有 c[x’] 也加上 p。
  • 若 c[x’] 维护区间积,修改种类是将 a[x] 乘上 p,则修改方式则是将所有 c[x’] 也乘上 p。
  • 然而,单点修改的自由性使得修改的种类和维护的信息不一定是同种运算,比如,若 c[x’] 维护区间和,修改种类是将 - a[x] 赋值为 p,可以考虑转化为将 a[x] 加上 p - a[x]。如果是将 a[x] 乘上 p,就考虑转化为 a[x] 加上 a[x] * p - a[x]。

下面以维护区间和,单点加为例给出实现。

void add(int x, int k) {
  while (x <= n) {  // 不能越界
    c[x] = c[x] + k;
    x = x + lowbit(x);
  }
}

总结

树状数组是一种用于维护数列前缀和的数据结构,它可以在 O(logn) 的时间复杂度内修改单个元素的值,以及查询某个区间的元素和。

至于为什么要学习这种数据结构,我的回答是,我在做leetcode算法题的时候,遇到类似问题了。而树状数组,是其中的一种解决方案,所以我就去特地学习了一下,并且得出了这么一篇文章。如果想要了解树状数组的更多知识了话,推荐大家去看这位大佬的文章:树状数组

这位大佬的语言比较高深,数学基础不好的人,不太容易看的懂。我这篇文章也是把这位大佬的文章截取一部分下来,做为我的文章。希望大家能够看得懂,并且学会什么叫做树状数组。

  • 最后的最后,如果大家觉得我这篇文章写的好的话,请关注我,给我个赞和收藏,您的支持,是我持续输出优质内容的无上动力!

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

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

相关文章

rancher2.7丢失集群信息

使用Docker 单节点安装rancher&#xff0c;然后在rancher中创建了一个k8s的集群。重启rancher所在的虚拟机后&#xff0c;登录rancher发现这是新的实例&#xff0c;集群信息丢失了。但是k8s集群还是好好的。 检查k8s的日志&#xff0c;api server日志会报错 time"2023-0…

11 - 多平台适配

多平台适配 11-1&#xff1a;开篇 在上一章中&#xff0c;我们知道了&#xff0c;当【慕课热搜】运行到 h5 端的时候&#xff0c;那么会出现一些问题&#xff0c;这些问题具体有&#xff1a; hot 列表滚动&#xff0c;tabs 置顶效果消失在火狐浏览器中&#xff0c;横线出现非…

kafka-kafka集群配置、kafka集群启动创建kafka主题、获取主题数据

本文章使用三台主机&#xff0c;分别为&#xff1a;master、slave1、slave2 一、解压kafka安装包至目录下 tar -zxvf kafka_2.12-2.4.1.tgz -C /需要放置的路径/ 二、修改配置文件 server.properties 该配置在kafka目录的config目录下 #修改文件中id数值 broker.id0 kafka集群…

Linux -- Web服务器-Apache 快速安装及主配置文件分析

目录 快速安装 Apache : 预处理 &#xff1a; 关闭安全上下文检测 : 关闭防火墙 : 启动 Apache 服务 &#xff08; 启动 httpd &#xff09;: 测试 &#xff1a; 主配置文件分析 &#xff1a; 常见配置文件所在位置 &#xff1a; 目录文件结构 &#xff1a;…

购物车--订单模块,练习完成

目标&#xff1a; 在购物车页面&#xff0c;增加一个创建订单的超链接。通过创建订单&#xff0c;在Order表里新增一条数据&#xff0c;同时把session中的订单条目都保存到数据库中。 1、创建两个表&#xff0c;orders用来具体存储每一个订单的细节&#xff0c;order_用来存储…

基于MobileNetV2的Transfer Learning模型,实现物体检测(附源码)

文章目录 一、MobileNet1. 深度可分离卷积&#xff08;Depthwise separable convolution&#xff09;2. MobileNet V13. MobileNet V2 二、物体检测源码&#xff08;基于MobileNetV2&#xff09; 一、MobileNet 1. 深度可分离卷积&#xff08;Depthwise separable convolution…

智慧园区综合管理平台开发基本功能有哪些?

随着智慧城市建设与信息化发展&#xff0c;园区管理也需要更加智能便捷化&#xff0c;于是智慧园区管理系统开发应运而生。智慧园区综合管理系统就是利用物联网、大数据等技术工具&#xff0c;顺应产业园区升级发展需求&#xff0c;实现园区环境、设备、安全、基础管理、资源服…

【Linux】进程间通信——命名管道

文章目录 命名管道1. 见一见管道文件mkfifo函数管道文件的使用 2. 命名管道原理如何保证两个毫不相关的进程&#xff0c;看到的是同一个文件&#xff0c;并打开&#xff1f; 3. 用命名管道实现server&client通信如何使用makefile连续生成可执行程序comm.hpp文件server.cc 服…

如何通过 Baklib 平台实现知识共享(内含案例介绍)

在这个信息时代&#xff0c;知识被认为是最重要的资源之一。企业要想保持发展&#xff0c;就必须善于利用和管理知识。而知识管理则是一种涵盖人、过程和技术的活动&#xff0c;它通过收集、整理、传递和应用知识&#xff0c;使组织获得更高的效率、创造力和竞争力。本文将以知…

【Linux】八、Linux进程信号详解(一)

目录 一、认识信号 1.1 生活中的信号 1.2 将1.1的概念迁移到进程 1.3 信号概念 1.4 查看系统定义信号列表 1.5 man 7 signal 1.6 解释1.2的代码样例 1.7 信号处理常见方式概览 二、产生信号 2.1 signal函数 2.2 通过终端按键产生信号 2.3 调用系统函数向进程发信号…

前后端的身份认证【Node.js】

1. 前后端的身份认证 1.1 Web 开发模式 目前主流的 Web 开发模式有两种&#xff0c;分别是&#xff1a; &#xff08;1&#xff09;基于服务端渲染的传统 Web 开发模式 &#xff08;2&#xff09;基于前后端分离的新型 Web 开发模式 服务端渲染的传统 Web 开发模式 服务端渲染…

力扣面试题 08.06. 汉诺塔问题:思路分析+图文详解+代码实现

文章目录 第一部分&#xff1a;问题描述1.1 题目1.2 示例&#x1f340; 示例一&#x1f340; 示例二 1.3 提示 第二部分&#xff1a;思路分析第三部分&#xff1a;代码实现 第一部分&#xff1a;问题描述 1.1 题目 &#x1f3e0; 链接&#xff1a;面试题 08.06. 汉诺塔问题 -…

windows安装rabbitmq和环境erlang(最详细版,包括对应关系)

写在最前&#xff1a;不知何时起安装一个mq需要翻无数文章才能安上了&#xff0c;没有一个讲全的&#xff0c;这里写一个详细的教程。 删除旧版本对应关系: 1.在官方文档中找到RabbitMQ版本对应的Erlang版本重新下载安装包文档RabbitMQ Erlang Version Requirements — Rabbit…

大家副业都在做什么?csgo搬砖靠谱的副业推荐给你

从来没想过&#xff0c;以前只会玩CSGO的男孩子&#xff0c;现在居然能借助游戏赚到钱了&#xff01;甚至不需要什么专业的技巧&#xff0c;简简单单 在steam平台选择有利润的道具后&#xff0c;再上架到国内网易BUFF平台&#xff0c;赚取“信息差”差价而已&#xff01; 谁大…

itop-3568开发板驱动学习笔记(19)内核工作队列

《【北京迅为】itop-3568开发板驱动开发指南.pdf》 学习笔记 文章目录 工作队列简介共享工作队列工作结构体初始化 work_struct调度工作队列函数共享工作队列实验 自定义工作队列创建工作队列函数调度和取消调度工作队列刷新工作队列函数删除工作队列函数 内核延时工作队列延时…

成功上岸字节35K,技术4面+HR面,耗时20天,真是不容易

这次字节的面试&#xff0c;给我的感触很深&#xff0c;意识到基础的重要性。一共经历了五轮面试&#xff1a;技术4面&#xff0b;HR面。 下面看正文 本人自动专业毕业&#xff0c;压抑了五个多月&#xff0c;终于鼓起勇气&#xff0c;去字节面试&#xff0c;下面是我的面试过…

kali利用airgeddon破解WiFi (详细安装和使用教程)

目录 前言 一&#xff0c;软件&硬件环境 二&#xff0c;前期配置 Airgeddon安装和调试 #自带 #安装方法一 #安装方法二 #注意 网卡的配置 #打开服务 #加载网卡 三&#xff0c;运行操作 #检查 #主菜单 #打开监听模式 #查看周围可以攻击的网络 #截取…

vue - - - - - vue3全局配置挂载axios

vue3配置axios 1. 安装axios2. 配置拦截器3. vue.config.js代理配置4. 将axios全局挂载4. 文件内使用 1. 安装axios yarn add axios 2. 配置拦截器 创建文件 /src/utils/request.js "use strict";import Vue from "vue"; import axios from "axios&…

从现在起,请你不要用ChatGPT再做这4件事了

ChatGPT已经火爆了快半年了吧&#xff0c;紧接着国内也开始推出了各种仿制品&#xff0c;我甚至一度怀疑&#xff0c;如果人家没有推出ChatGPT&#xff0c;这些仿制品会不会出现。而很多人也嗨皮得不行&#xff0c;搭着梯子爬过高墙&#xff0c;用ChatGPT做各种觉得新鲜的事。但…

电脑可以开机但是无法进入到桌面怎么办?

电脑可以开机但是无法进入到桌面怎么办&#xff1f;有用户的电脑可以正确启动&#xff0c;但是电脑启动之后&#xff0c;却无法进入到系统桌面&#xff0c;而且卡在加载系统的页面中&#xff0c;或者是出现错误代码蓝屏了。这些情况其实都可以通过U盘来重装一个系统&#xff0c…