小木的算法日记-线段树

🌳 线段树 (Segment Tree):玩转区间作的终极利器

你好,未来的算法大师!

想象一下,你正在处理一个巨大的数据集,比如某个电商网站一整天的用户点击流。老板突然问你:“下午2点到3点之间,我们的总点击量是多少?” 几分钟后,他又问:“把10点到11点之间的数据,因为系统故障,全部乘以0.5,然后再告诉我下午的总点击量。”

如果用普通的数组,每次查询都需要遍历,每次修改更是灾难。面对这种需求,我们该怎么办?频繁的区间查询和区间修改

答案,就是我们今天的主角——。线段树 (Segment Tree)

这篇文章将带你:

  1. 直击痛点:理解普通数组在区间问题上的局限性。

  2. 掌握神器:了解线段树如何用的时间复杂度解决这些问题。O(对数 N)

  3. 展望进阶:一窥线段树的“动态开点”和“懒更新”等高级玩法。

准备好了吗?让我们开始构建这棵神奇的“信息之树”。


😫 The Pain: 区间问题的困境

在深入线段树之前,我们先感受一下“没有线段树”时的痛苦。

假设我们有一个数组,需要频繁查询任意区间的最小值。一个常见的优化思路是。比如,我们可以创建一个数组,其中存储这个后缀区间的最小值。数字预计算后缀最小后缀Min[i]nums[i:]

      # 预计算后缀最小值的示例
nums = [3, 1, 4, 2]
# suffixMin[i] 表示 nums[i..] 中的最小值
suffixMin = [0] * len(nums)  :创建一个和 nums 长度相同的数组,用来存每个位置的「后缀最小值」# 从后往前计算
suffixMin[len(nums) - 1] = nums[len(nums) - 1]  作用:最后一个人的后缀只有自己,所以他的「后缀最小值」就是自己的身高。
for i in range(len(nums) - 2, -1, -1):suffixMin[i] = min(nums[i], suffixMin[i + 1])# suffixMin 会是 [1, 1, 2, 2]
print(suffixMin)# 查询 nums[1..] 的最小值
# O(1) 时间查询,非常快!
print(suffixMin[1]) # 输出 1

这种“前缀和/后缀和”思想很棒,查询效率是,但它有一个:O(1)致命弱点

无法应对动态修改!

一旦中的某个元素改变,比如变了,那么和都需要重新计算。如果数组很大,一次修改可能导致的更新成本,这在高性能场景下是不可接受的。数字数字[1]后缀Min[0]后缀Min[1]O(N)

痛点总结

  • 静态数组:区间查询慢 (),单点更新快 ()。O(N)O(1)

  • 预计算数组:特定区间查询快 (),但更新慢 ()。O(1)O(N)

我们需要一个既能快速查询,又能快速更新的数据结构。于是,线段树应运而生。


✨ The Magic: 线段树的核心能力

一句话定义:线段树是一种,它将一个区间分解成若干个子区间,每个节点代表一个区间的“聚合信息”(如和、最值等),从而实现快速的区间操作。平衡二叉树

核心 API 概览 (⭐)

一个基础的线段树通常提供以下接口:

      from typing import Callableclass SegmentTree:def __init__(self, nums: list[int], merge: Callable[[int, int], int]):"""构造函数,基于一个数组初始化线段树。时间复杂度: O(N)Args:nums (list[int]): 原始数组。merge (Callable): 一个函数,定义了如何合并两个子区间的信息。例如,求和是 lambda a, b: a + b,求最小值是 lambda a, b: min(a, b)。"""passdef query(self, i: int, j: int) -> int:"""查询闭区间 [i, j] 的聚合值。时间复杂度: O(log N)"""passdef update(self, i: int, val: int):"""将数组中索引 i 的值更新为 val。时间复杂度: O(log N)"""pass

为什么是 ?O(log N)

  1. 树高是 O(log N):线段树在构建时,总是从中间分割区间,这保证了它是一棵的二叉树。树的高度决定了操作的上限。高度平衡

  2. query 的路径:查询一个区间 时,这个大区间可以被巧妙地拆分成树上 个节点所代表的小区间。我们只需将这些节点的信息合并即可。[i, j]O(log N)

  3. update 的路径:更新一个叶子节点时,我们只需要沿着从该叶子到根节点的唯一路径,更新路径上的所有父节点。这条路径的长度就是树的高度,即 。O(log N)


🚀 The Evolution: 线段树的进阶形态

基础线段树已经很强大了,但在更复杂的场景下,它还有一些更酷的变体。

变体名称重要性解决的问题核心技巧
基础线段树⭐⭐⭐⭐⭐区间查询 & 单点更新。面试和竞赛的基础。分治思想
动态开点线段树⭐⭐⭐⭐处理,如 ,节省内存。超大稀疏区间[0, 10^9]不预先建树,访问到节点时才创建。
懒更新线段树⭐⭐⭐⭐⭐区间更新。例如,将 内所有数加 。[i, j]val将更新任务“缓存”在父节点上,需要时再下推。

懒更新 (Lazy Propagation) 举例:
想象一下,要把 [0, 100] 这个区间的所有数都加上 5。我们不必真的去更新 101 个叶子节点。我们只需在代表 的那个根节点上贴一个标签:“嘿,我这里的所有子孙后代都要加 5”。[0, 100]

只有当查询操作需要深入到这个节点的子区间时,我们才把这个“懒任务”向下传递一层。这个技巧极大地提升了区间更新的效率,使其也能达到 。O(log N)


🧠 学习成果检验 (必会)

现在,检验一下你是否掌握了线段树的核心思想。请尝试回答以下问题:

问题 1:
为什么说线段树是一种“空间换时间”的数据结构?它比原始数组多占用了多少空间?

一句话总结
线段树就像为数组建了一个 “多层抽屉柜”,用更多抽屉(空间)换更快的查找速度(时间)。

 

详细解释

 
  • 原始数组:比如有 4 个苹果(N=4),放在一排抽屉里,想找区间最小值时,得逐个翻找(O (N) 时间)。
  • 线段树:把 4 个苹果放进一个 3 层的抽屉柜:
    • 第 1 层:1 个抽屉(根节点),存 “所有苹果的最小值”。
    • 第 2 层:2 个抽屉,分别存 “前 2 个苹果” 和 “后 2 个苹果” 的最小值。
    • 第 3 层:4 个抽屉,每个抽屉存 1 个苹果(叶子节点)。
    • 总抽屉数:1+2+4=7 个,约是原始数组的 2 倍(实际线段树通常开 4 倍空间,避免越界)。

空间占用结论

 
  • 线段树需要 4 倍原始数组大小 的空间(比如 N=100,线段树用 400 个位置),但换来每次查询 / 更新只需 “爬 3 层楼”(O (logN) 时间),比原始数组的 “翻 100 次抽屉”(O (N))快很多!

问题 2:
如果我想用线段树来解决“查询区间 内有多少个偶数”的问题,我应该如何定义 中的 函数和叶子节点的值?

比喻:分糖果统计
假设每个数字是一颗糖,偶数糖是粉色(值为 1),奇数糖是蓝色(值为 0)。线段树的任务是统计任意区间内的粉色糖数量。

具体做法

  1. 叶子节点:每个叶子对应一个数字,存 “1”(粉色)或 “0”(蓝色)。
    • 比如数字4(偶数)→ 叶子存1;数字3(奇数)→ 叶子存0
  2. merge 函数:父节点把左右子节点的数加起来,就像把两堆糖合起来数总数。
    • 左子树有 2 颗粉色糖,右子树有 1 颗→ 父节点存 3 颗。

举个例子
数组[2, 3, 4, 5] → 叶子节点是[1, 0, 1, 0]

  • 左子树(前两个数)的和是1+0=1(表示有 1 个偶数)。
  • 右子树(后两个数)的和是1+0=1
  • 根节点的和是1+1=2,即整个数组有 2 个偶数。

问题 3:
一个朋友告诉你:“我可以用 个预计算的前缀和数组,在 时间内查询任意区间的和,并且支持 的单点更新,这比线段树的 查询要快!” 他的说法在什么情况下可能是合理的,又在什么情况下线段树是更优的选择?NO(1)O(N)O(log N)

比喻:图书馆查书 vs 改书

 
  • 朋友的方法(前缀和)

    • 提前写好一本 “查书手册”,记录从第 1 页到第 i 页的总字数(前缀和)。
    • 查书快:想知道第 3 页到第 5 页的总字数,直接用手册计算(O (1) 时间)。
    • 改书慢:如果修改第 4 页的字数,需要重写第 4 页之后的所有手册内容(O (N) 时间)。
    • 适用场景:图书馆的书很少修改(更新少),但每天有很多人查询(查询多)。
  • 线段树的方法

    • 把书分成章节存进 “多层书架”,每层记录对应章节的总字数。
    • 查书和改书都快:修改第 4 页只需更新对应章节的书架(O (logN) 时间),查询时逐层合并结果(O (logN) 时间)。
    • 适用场景:如果书经常被修改(比如作业答案频繁更新),线段树更省力。
 

结论

 
  • 朋友的方法适合 “查询多、修改少”(如历史数据统计)。
  • 线段树适合 “查询和修改都频繁”(如实时游戏数据、动态榜单)。
    就像:
  • 查字典用目录(前缀和)很快,但字典印好后不能改;
  • 动态更新的电子表格用线段树结构,改一个数只影响附近区域,更灵活。

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

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

相关文章

Day24 元组和OS模块

1、元组(有序 不可变 可重复) 管道工程中pipeline类接收的是一个包含多个小元组的列表作为输入。可以这样理解这个结构: (1) 列表 []: 定义了步骤执行的先后顺序。Pipeline 会按照列表中的顺序依次处理数据。之所以用列…

Auto-Coder使用GPT-4o完成:在用TabPFN这个模型构建一个预测未来3天涨跌的分类任务

通过akshare库,获取股票数据,并生成TabPFN这个模型 可以识别、处理的格式,写一个完整的预处理示例,并构建一个预测未来 3 天股价涨跌的分类任务 用TabPFN这个模型构建一个预测未来 3 天股价涨跌的分类任务,进行预测并输…

Device Mapper 机制

Device Mapper 机制详解 Device Mapper(简称 DM)是 Linux 内核中的一套通用块设备映射框架,为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程,并配以详细的…

crackme006

crackme006 名称值软件名称aLoNg3x.1.exe加壳方式无保护方式Serial编译语言Delphi调试环境Win10 64位使用工具x32dbg,ida pro,PEid,DarkDe4破解日期2025-06-05 脱壳 1. 先用PEid查壳 查到无壳 寻找Serial 查询到编程语言为Delphi 导出Delphi符号表信息到x32dbg&#xff0c…

Conda 创建新环境时报错 HTTP 502,如何解决?

Conda 创建新环境时报错 HTTP 502&#xff0c;如何解决&#xff1f; 最近在用 Conda 创建新环境时&#xff0c;突然遇到这样一个错误&#xff1a; CondaHTTPError: HTTP 502 BAD GATEWAY for url <https://mirrors.westlake.edu.cn/ANACONDA/cloud/conda-forge/linux-64/r…

2025最全TS手写题之partial/Omit/Pick/Exclude/Readonly/Required

随着 TS 在工作中使用的越来越广泛&#xff0c;面试的时候面试官也都会加上一两个 TS 的问题来了解候选人对于 TS 的熟悉程度&#xff0c;其中就有不少手写题目&#xff0c;比如笔者在字节的一次二面&#xff0c;面试官就问到了我如何实现一个 Pick&#xff0c;在小红书的一面&…

基于江科大stm32屏幕驱动,实现OLED多级菜单(动画效果),结构体链表实现(独创源码)

引言 在嵌入式系统中&#xff0c;用户界面的设计往往直接影响到用户体验。本文将以STM32微控制器和OLED显示屏为例&#xff0c;介绍如何实现一个多级菜单系统。该系统支持用户通过按键导航菜单&#xff0c;执行相应操作&#xff0c;并提供平滑的滚动动画效果。 本文设计了一个…

LLMs之StructuredOutput:大模型结构化输出的简介、常用方案、前沿框架之详细攻略

LLMs之StructuredOutput&#xff1a;大模型结构化输出的简介、常用方案、前沿框架之详细攻略 目录 大模型结构化输出的简介 1、特点与难点 大模型结构化输出的常用方案及对比 1、前沿框架&#xff1a;vLLM 与 XGrammar 大模型结构化输出的案例应用 大模型结构化输出的简介…

Linux中shell流程控制语句

一、if条件控制 1.1 语法解读 单路决策 - 单分支if语句样式&#xff1a;if [ 条件 ]then指令fi特点&#xff1a;单一条件&#xff0c;只有一个输出 双路决策 - 双分支if语句样式&#xff1a;if [ 条件 ]then指令1else指令2fi特点&#xff1a;单一条件&#xff0c;两个输出 …

Python学习(8) ----- Python的类与对象

Python 中的类&#xff08;Class&#xff09;与对象&#xff08;Object&#xff09;是面向对象编程&#xff08;OOP&#xff09;的核心。我们可以通过“类是模板&#xff0c;对象是实例”来理解它们的关系。 &#x1f9f1; 一句话理解&#xff1a; 类就像“图纸”&#xff0c;对…

数据结构-文件

文件是性质相同的记录的集合。 记录是文件中存取的基本单位&#xff0c;数据项是文件可使用的最小单位。 操作系统研究的文件是一维的无结构连续字符序列&#xff0c;数据库中研究的文件是带有结构的记录集合。 文件在外存上的4种基本组织方式&#xff1a;顺序、索引、散列、链…

前端开发面试题总结-CSS篇

文章目录 CSS面试高频问答1、CSS选择器的优先级2、CSS3新特性3、如何垂直水平居中盒子4、什么是重绘和重排5、px/em/rem/vw有什么区别6、rem布局的原理7、如何设置比12px还要小的字体8、CSS中隐藏元素的方式有哪些 CSS面试高频问答 1、CSS选择器的优先级 2、CSS3新特性 3、如何…

Python如何给视频添加音频和字幕

在Python中&#xff0c;给视频添加音频和字幕可以使用电影文件处理库MoviePy和字幕处理库Subtitles。下面将详细介绍如何使用这些库来实现视频的音频和字幕添加&#xff0c;包括必要的代码示例和详细解释。 环境准备 在开始之前&#xff0c;需要安装以下Python库&#xff1a;…

解决ubuntu20.04无法唤醒的问题的一种方法

解决ubuntu20.04无法唤醒的问题的一种方法 我更改了三个个地方&#xff0c;目前不清楚是哪个地方起的作用&#xff0c;也可能都起作用了 修改的第一个地方 步骤 1: 获取 Swap 分区的 UUID 首先&#xff0c;你需要知道你的 swap 分区的 UUID。你可以使用以下命令来查找它&am…

【大厂机试题解法笔记】矩阵匹配

题目 从一个 N * M&#xff08;N ≤ M&#xff09;的矩阵中选出 N 个数&#xff0c;任意两个数字不能在同一行或同一列&#xff0c;求选出来的 N 个数中第 K 大的数字的最小值是多少。 输入描述 输入矩阵要求&#xff1a;1 ≤ K ≤ N ≤ M ≤ 150 输入格式 N M K N*M矩阵 输…

Kubernetes 网络模型深度解析:Pod IP 与 Service 的负载均衡机制,Service到底是什么?

Pod IP 的本质与特性 Pod IP 的定位 纯端点地址&#xff1a;Pod IP 是分配给 Pod 网络命名空间的真实 IP 地址&#xff08;如 10.244.1.2&#xff09;无特殊名称&#xff1a;在 Kubernetes 中&#xff0c;它通常被称为 “Pod IP” 或 “容器 IP”生命周期&#xff1a;与 Pod …

使用osqp求解简单二次规划问题

文章目录 一、问题描述二、数学推导1. 目标函数处理2. 约束条件处理 三、代码编写 一、问题描述 已知&#xff1a; m i n ( x 1 − 1 ) 2 ( x 2 − 2 ) 2 s . t . 0 ⩽ x 1 ⩽ 1.5 , 1 ⩽ x 2 ⩽ 2.5 min(x_1-1)^2(x_2-2)^2 \qquad s.t. \ \ 0 \leqslant x_1 \leqslant 1.5,…

pe文件结构(TLS)

TLS 什么是TLS? TLS是 Thread Local Storage 的缩写&#xff0c;线程局部存储。主要是为了解决多线程中变量同步的问题 如果需要要一个线程内部的各个函数调用都能访问&#xff0c;但其它线程不能访问的变量&#xff08;被称为static memory local to a thread 线程局部静态变…

Electron简介(附电子书学习资料)

一、什么是Electron&#xff1f; Electron 是一个由 GitHub 开发的 开源框架&#xff0c;允许开发者使用 Web技术&#xff08;HTML、CSS、JavaScript&#xff09; 构建跨平台的桌面应用程序&#xff08;Windows、macOS、Linux&#xff09;。它将 Chromium浏览器内核 和 Node.j…

如何使用DeepSeek帮助自己的工作?(Java开发)

如何使用DeepSeek帮助自己的工作&#xff1f; 作为Java开发者&#xff0c;你可以通过以下方式高效利用DeepSeek提升工作效率&#xff08;附具体操作示例&#xff09;&#xff1a; 一、日常编码加速 1. 代码生成与补全 // 输入需求描述&#xff1a; "生成SpringBoot分页…