线段树详解(附图解,模板及经典例题分析)

article/2025/9/30 2:04:26

1.基本概念

​ 线段树是一种常用来维护 区间信息 \color{Maroon}区间信息 区间信息 的数据结构

​ 可以在 O(logn) 的时间复杂度内实现单点修改、区间修改、区间查询(区间求和,求区间的最大值,求区间的最小值)等操作

​ 如下图所示,线段树是建立在区间基础上的树,树的每个节点都代表着一段区间【L,R】

在这里插入图片描述

对于每个段区间【L,R】来说,我们注意到

(1)若 L = R,说明这个结点只有一个点,那么它就是一个叶子节点

(2)若 L < R,说明这个节点代表的不止一个点,此时它就会有两个儿子

  • 左儿子:区间【L,M】
  • 右儿子:区间【M + 1,R】

其中 M = (L + R)/ 2

在实现时,我们考虑 递归建树 \color{CadetBlue}递归建树 递归建树 。即,设当前根结点为 u

  • 如果根结点管辖的区间长度已经为 1,则可以直接根据原数组a上相应位置的值初始化该节点
  • 否则将该区间从中点处分割为两个子区间【L,M】,【M + 1,R】,并分别进入左右节点的递归建树,最后将两个子节点的信息pushup到父节点u
struct Node// 线段树结构
{int l, r;int sum;// 【l, r】的区间和}tr[4 * N];
void pushup(int u)// 将信息往上传递,在这里维护的是区间和
{tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;}
void build(int u, int l, int r)
{if(l == r) tr[u] = {l, r, a[r]};else{tr[u] = {l, r}; // 注意此行,若不初始化,会TLEint mid = l + r >>1;build(u << 1, l, mid);// 左儿子build(u << 1 | 1, mid + 1, r);// 右儿子pushup(u);// 将信息传递给父节点}}

思考:为什么对于线段树的数组空间我们要开到4倍的 N呢?

​ 如果采用堆式存储( 2u 是 u 的左儿子, 2u + 1 是 u 的右儿子),若有 n 个叶子节点,则 tr 数组的范围最大为 2 ⌈ l o g n ⌉ + 1 2^{\lceil{logn}\rceil + 1} 2logn+1

​ 容易知道线段树的深度是 ⌈ l o g n ⌉ \lceil{logn}\rceil logn 的,则在堆式情况下叶子节点(包括没有用到的叶子节点)数量为 2 ⌈ l o g n ⌉ 2^{\lceil{logn}\rceil} 2logn

​ 我们可以发现 2 ⌈ l o g n ⌉ + 1 n \frac{2^{\lceil{logn}\rceil + 1}}{n} n2logn+1 最大值在 n = 2 x + 1 ( x ∈ N + ) n = 2^x + 1 (x ∈ N_+) n=2x+1(xN+) 时取到,此时节点数为

2 ⌈ l o g n ⌉ + 1 − 1 = 2 x + 2 − 1 = 4 n − 5 2^{\lceil{logn}\rceil + 1 } - 1 = 2^{x + 2} - 1 = 4n - 5 2logn+11=2x+21=4n5 ,故我们可以直接开 4n 的 tr数组,减少计算时间

2.原理推导

( 1 ) 区间查询 − − O ( l o g n ) \color{Turquoise}(1) 区间查询 - - O(logn) (1)区间查询O(logn)

1. 若这个线段树区间被完全包括在目标区间里面,则直接返回这个区间的值 \color{skyBlue}1.若这个线段树区间被完全包括在目标区间里面,则直接返回这个区间的值 1.若这个线段树区间被完全包括在目标区间里面,则直接返回这个区间的值

在这里插入图片描述

if(tr[i].l >= l && tr[r].r <= r) // 线段树区间被完全包含
return tr[u].sum;

2. 若这个线段树区间的左儿子与目标区间有交集,那么搜索左儿子 \color{skyBlue}2.若这个线段树区间的左儿子与目标区间有交集,那么搜索左儿子 2.若这个线段树区间的左儿子与目标区间有交集,那么搜索左儿子

在这里插入图片描述

int sum = 0;
if(l <= mid) sum = query(u << 1, l, r); // 与左儿子有交集

3. 若这个线段树区间的右儿子与目标区间有交集,那么搜索右儿子 \color{skyBlue}3.若这个线段树区间的右儿子与目标区间有交集,那么搜索右儿子 3.若这个线段树区间的右儿子与目标区间有交集,那么搜索右儿子

在这里插入图片描述

int sum = 0;
// if(l <= mid) sum = query(u << 1, l, r); 与左儿子有交集
if( r > mid) sum += query(u << 1 | 1, l, r); // 与右儿子有交集

例如,如下图所示,如果要查询的区间为 【1,5】的和,那么可以直接获取Tr[1] 的值(15)即可

在这里插入图片描述

如果要查询的区间为【3,5】,此时就不能直接获取区间的值,但是【3,5】可以拆分成【3,3】【4,5】,可以通过合并这两个区间的答案来求得这个区间的和

一般地,如果要查询的区间为【l,r】,则可以将其拆成至多为 O(logn)极大的区间,合并这些区间即可求出区间【l,r】的答案

Code

int query(int u, int l, int r)// u 为父节点,区间【l,r】为目标区间
{if(tr[u].l >= l && tr[u].r <= r) return tr[u].sum;// 如果线段树区间被完全包括在目标区间里面int sum = 0;int mid = tr[u].l + tr[u].r >> 1;// 左儿子与目标区间有交集if(l <= mid) sum = query(u << 1, l, r);// 因为sum初始化为 0,所以不写 += 也可以//右儿子与目标区间有交集if(r > mid) sum += query(u << 1 | 1, l, r);return sum;
}

( 2 ) 单点修改 − − O ( l o g n ) \color{Turquoise}(2) 单点修改- - O(logn) (2)单点修改O(logn)

​ 单点修改,换句话说就是如何实现点更新,假使我们要将 原数组 a[3] + 7 ,则更新后的线段树应该变成

在这里插入图片描述

​ 也就是说,当我们更新原数组a[]某一个位置 x 的数值时,在线段树中包含此值的区间都需要被更新,那么有多少个节点需要更新呢,也就是最大要达到多深才完成全部区间的更新呢?根据二叉树的性质,容易知道线段树的深度是 ⌈ l o g n ⌉ \lceil{logn}\rceil logn的,故此每次更新的时间复杂度为O(logn)

​ 对于每次的更新,我们发现,无论你更新的是哪一个节点,最终都会将信息pushup到根结点,而把这个往上推的过程逆过来就是 从根结点开始,找到左子树或者右子树中包含需要更新的叶子节点,自顶向下更新即可 \color{Orange}从根结点开始,找到左子树或者右子树中包含需要更新的叶子节点,自顶向下更新即可 从根结点开始,找到左子树或者右子树中包含需要更新的叶子节点,自顶向下更新即可,所以我们还是采用递归的方法实现线段树的点更新

Code

void modify(int u, int x, int v) // 根结点,插入的位置,插入的值
{if(tr[u].l == tr[u].r) tr[u].sum += v;else{int mid = tr[u].l + tr[u].r >> 1;if(x <= mid) modify(u << 1, x, v);// 左子树包含需要更新的叶子节点else modify(u << 1 | 1, x, v); // 右子树包含需要更新的叶子节点pushup(u);}
}

3.初步总结

目前为止,我们就把最基本的线段树操作过了一遍,必须抓住 四个核心函数 \color{Red}四个核心函数 四个核心函数

  • p u s h u p ( i n t u ) \color{Green}pushup~~\color{CadetBlue}(int ~u) pushup  (int u) :用子节点信息更新当前父节点信息
  • b u i l d ( i n t u , i n t l , i n t r ) \color{Maroon}build ~~\color{CadetBlue}(int ~u,~int ~ l, ~ int~r) build  (int u, int l, int r) : 在一段区间上,初始化线段树
  • m o d i f y ( i n t u , i n t x , i n t v ) \color{purple}modify~\color{CadetBlue}(int ~u,~int ~ x, ~ int~v) modify (int u, int x, int v): 修改目标位置 x 的属性
  • q u e r y ( i n t u , i n t l , i n t r ) \color{Turquoise}query~~\color{CadetBlue}(int ~u,~int ~ l, ~ int~r) query  (int u, int l, int r): 查询区间【L, R】的属性(max,区间和,min等等)

细节 : 线段树的下标从 1 开始

4.线段树拓展及其例题


以上内容尚未完全,随着今后学习的推进,我会继续对其进行补充与完善。

【预告】

  1. 线段树拓展
  2. 经典例题

http://chatgpt.dhexx.cn/article/q3GliqH6.shtml

相关文章

线段树详解

文章目录 1. 什么是线段树2. 线段树的用处3. 线段树的模板题4. 动态开点线段树5. 权值线段树5. 可持久化线段树 1. 什么是线段树 顾名思义 &#xff0c; 线段树是一棵二叉树 &#xff0c; 但不同的是这棵树的结点储存的值是一个数列中 [ l , r ] [l,r] [l,r] 的某个需要的值 …

超详解线段树(浅显易懂,几乎涵盖所有线段树类型讲解,匠心之作,图文并茂)

一&#xff0c;什么是线段树&#xff1f; 线段树是怎样的树形结构? 线段树是一种二叉搜索树&#xff0c;而二叉搜索树&#xff0c;首先满足二叉树&#xff0c;即每个结点最多有两颗子树&#xff0c;并且是一颗搜索树&#xff0c;我们要知道&#xff0c;线段树的每个结点都存储…

线段树 从入门到进阶(超清晰,简单易懂)

目录 第一部 概念引入第二部 简单&#xff08;无pushdown&#xff09;的线段树1、单点修改&#xff0c;区间查询2、区间修改&#xff0c;单点查询 第三部 进阶线段树第四部 乘法&#xff08;根号&#xff09;线段树1、乘法线段树2、根号线段树模板题与代码&#xff1a;单点修改…

Navicat Premium 数据库开发工具

官网地址&#xff1a;https://www.navicat.com.cn/ Navicat Premium 是一套数据库开发工具&#xff0c;让你从单一应用程序中同时连接 MySQL、MariaDB、MongoDB、SQL Server、Oracle、PostgreSQL 和 SQLite 数据库。它与 Amazon RDS、Amazon Aurora、Amazon Redshift、Microso…

Navicat 一款数据库开发软件

Navicat premium是一款数据库管理工具&#xff0c;非常好用&#xff0c;能复制表结构等&#xff0c;大大简化了后端开发的工程量&#xff0c;其软件示意图如下 Navicat Premium 15 已经发布了&#xff0c;但是价格昂贵&#xff0c;所以将 no-money version 的双手奉上。 使用…

Java学习中的数据库和数据库开发工具

一、数据库 1、数据库&#xff0c;通常是一个戒一组文件&#xff0c;保存了一些符合特定规格的数据&#xff0c;数据库对应的英询单词是DataBase&#xff0c;简称DB&#xff1b;数据库软件称为数据库管理系统&#xff0c;英文简称DBMS&#xff0c;全称为DataBase Management S…

五大主流数据库深度对比!数据库开发、管理看这篇就够了

开发数据库应用&#xff0c;选择一个好的数据库是非常重要的。目前&#xff0c;商品化的数据库管理系统以关系型数据库为主导产品&#xff0c; 技术比较成熟。面向对象的数据库管理系统技术先进&#xff0c;数据库易于开发、维护。无论是关系型数据库还是非关系型数据库&#x…

mysql数据库开发环境_MySQL数据库教程-环境与集成开发工具

MySQL数据库基础教程 数据库是存储数据与管理数据的基础,也是目前绝大多数动态网站开发所必备的基础知识。MySQL数据库教程系列文章主要以MySQL数据库管理系统为例对关系型数据库相关定义与操作等进行讲解。并结合php与PDO等讲解,为初学者提供快速学习PHP动态网站开发所需的知…

C/C++ MySQL数据库开发

C/C++ MySQL数据库开发笔记 MySQL启动方式手动启动MySQL数据库:命令行方式启动MySQL数据库:配置环境变量MySQL在Linux下安装MySQL的配置文件重要参数设置C/C++ 访问MySQL数据库(MySQL开发环境的配置(MySQL开发头文件和库文件)MySQL 数据库的连接MySQL 数据类型以及对应的 …

1、 数据库开发规范

第1章 数据库开发规范的制定 数据库设计步骤&#xff1a; 1、数据结构设计&#xff1a;逻辑设计-》物理设计 2、实际工作中&#xff1a;逻辑设计物理设计 3、物理设计&#xff1a;表名字段名字段类型 数据库设计几个规范&#xff1a; 数据库命名规范、数据库基本设计规范…

数据库开发-2-开发数据库的要点

Lec2-开发数据库的要点 1. 开发成功数据库应用的特点 需要理解数据库体系结构需要理解锁和并发控制特性&#xff1a;每个数据库都以不同的方式实现最重要的是不要把数据库当"黑盒" 要了解每一个数据库的体系结构和特征最常见的问题就是因为我们对数据库本身了解不足…

数据库开发(Sqlite)

1、数据库开发 1.1 数据与数据管理 什么是信息&#xff1f;   信息是指对现实世界存在方式或运动状态的反应。 什么是数据&#xff1f;   数据是指存储在某一媒体上&#xff0c;能够被识别的物理符号&#xff1b;   数据的概念在数据处理领域已经被大为拓宽&#xff0c…

《软件测试的艺术》第4章:测试用例的设计-白盒测试

写在前面&#xff1a;原书中包含白盒测试、黑盒测试、错误猜测、测试策略四个小节&#xff0c;涵盖内容较多&#xff0c;因此按章节拆分叙述。 《软件测试的艺术》&#xff1a; 白盒测试--语句覆盖 语句覆盖的用例设计原则&#xff1a;将程序中的每条语句至少执行一次。 白盒…

《软件测试的艺术》读后感 Or 读书笔记

《软件测试的艺术》读后感 Or 读书笔记 第一章 一次自评价测试第二章 软件测试的心理学和经济学第三章 代码检查、走查与评审第四章 测试用例的设计第五章 模块&#xff08;单元&#xff09;测试第六章 更高级别的测试第七章 可用性&#xff08;或用户体验&#xff09;测试第八…

软件测试的艺术 学习笔记

文章目录 4.2黑盒测试4.2.1 等价划分4.2.2 边界值分析4.2.3 因果图 4.3 错误猜测4.4 测试策略 5. 模块(单元&#xff09;测试5.1 测试用例设计5.2 增量测试5.3 自顶向下测试与自底向上测试5.3.1 自顶向下的测试5.3.2 自底向上的测试5.3.3 比较 5.4 执行测试 6 更高级别的测试6.…

《软件测试的艺术》第六章 更高级别的测试

《软件测试的艺术》第六章 更高级别的测试 6.0 前言软件开发过程模型 6.1 功能测试6.2 系统测试6.2.1 能力测试6.2.2 容量测试6.2.3 强度测试6.2.4 可用性测试6.2.5 安全性测试6.2.6 性能测试6.2.7 存储测试6.2.8 配置设置6.2.9 兼容性/转换测试6.2.10 安装测试6.2.11 可靠性测…

模块测试(单元测试)——软件测试的艺术

是大型程序测试的第一个步骤【大型程序即超过500条语句的程序】 了解 模块测试是对程序中的单个程序、子程序/过程进行测试的过程【并非对整个程序】&#xff1a; 关注点在较小单元&#xff0c;是一种管理组合的测试元素的手段减轻调试的难度&#xff0c;把错误定位到一个小…

《软件测试的艺术》第1章:一次自评价测试

写在前面&#xff1a; 相比于芯片验证&#xff0c;软件测试有着悠久的历史沉淀和更为完整的生态&#xff0c;和芯片验证在某些方面上几乎有着相同的思路和方法。因此从软件测试的视角出发&#xff0c;重新思考芯片验证的方方面面。第一个系列为《软件测试的艺术》学习。 第一…

9年测试老鸟:Glenford J编写《软件测试的艺术》PDF,高清中文版

内容简介 本书以一次自评价测试开篇&#xff0c;从软件测试的心理学和经济学人手&#xff0c;探讨了代码检查、走查与评审、测试用例的设计、模块(单元)测试、系统测试、调试等主题&#xff0c;以及极限测试、因特网应用系统测试等高级主题&#xff0c;全面展现了作者的软件测…

系统测试——软件测试的艺术

系统测试有着特定的目的&#xff1a;将系统或程序与其初始目标进行比较&#xff0c;给定目标后有两含义&#xff1a; 系统测试不局限于系统&#xff0c;若产品是一个程序&#xff1a;系统测试就是试图说明程序作为一个整体是如何不满足其目标的过程根据定义&#xff0c;若产品…