简单线段树的讲解(一点点的心得体会)

目录

一、初识线段树

图例:

​编辑

数组存储:

指针存储:

理由:

build函数建树

二、线段树的区间修改维护

区间修改维护:

区间修改的操作:

递归更新过程:

区间修改update:

三、线段树的区间查询

区间查询:

区间查询的操作:

递归查询过程:

区间查询query:

例题:

完整代码:

数组实现:

指针实现:

总结


前言

今天我们来学习一下线段树

模板题目:P3372 【模板】线段树 1 - 洛谷


一、初识线段树

首先我们来了解一下什么是线段树

      线段树是一种数据结构,通常用来解决区间查询的问题。它主要用于对一个包含有 n 个元素的数组进行区间操作,如查询某个区间内的最大值、最小值、区间和等。

       线段树的基本思想是将整个数组按照一定规则进行分割,每个节点代表一个区间。每个节点保存区间内的信息,如最大值、最小值、区间和等。父节点的信息可以通过子节点的信息合并得到,这样就可以快速进行区间查询。

      线段树通常是一棵完全二叉树,叶子节点对应于数组中的元素,每个非叶子节点表示了其区间的信息。对于一个包含 n 个元素的数组,线段树的节点数一般是 2n-1 或 2^k-1,其中 k 是大于等于 n 的最小整数。线段树的构建包括建树和更新两个主要操作,查询时可以通过递归的方式进行。

       线段树在解决区间查询问题时效率很高,时间复杂度一般为 O(logn),其中 n 是数组元素个数。因此,线段树被广泛应用于需要频繁进行区间查询的场景,如动态区间最值查询、区间和查询等。

我这次是联系模板题目P3372 【模板】线段树 1 - 洛谷讲解的最简单的类型

图例:

      通过这幅图我们可以看出,线段树是根据不断的从子节点拿值,来更新父节点的值,直到得到整个区间的值,和分治的思想有点像,感觉

线段树的存储方式有两种常见的实现方法:数组存储和指针存储。

  1. 数组存储:

    • 在数组存储中,线段树被表示为一个静态的完全二叉树。数组的下标从 1 开始,对于节点 i,其左子节点为 2i,右子节点为 2i+1。
    • 如果线段树的叶子节点数量为 n,那么数组的大小一般取 4n,以确保足够的空间。
    • 线段树根节点一般存储在数组下标为 1 的位置。
    • 通过按照规则在数组中存储线段树的节点,可以方便地进行查询和更新操作。
  2. 指针存储:

    • 在指针存储中,线段树被表示为一个动态的树结构,每个节点通过指针指向其左右子节点。
    • 每个节点通常由一个包含左右子节点指针的结构体或类表示。
    • 指针存储方式在构建线段树时会动态生成节点,相对于数组存储来说更加灵活,但可能会消耗更多的内存空间。

     无论是数组存储还是指针存储,线段树的基本操作都是相似的,包括建树、查询和更新。选择适合具体应用场景的存储方式可以更好地利用线段树的优势,提高算法效率。

      首先是数组存储,我们最先要知道的是数组的大小需要开多大,在线段树的数组存储中,通常会将数组的大小设置为 4n,其中 n 表示线段树的叶子节点数量。

理由:

  1. 完全二叉树性质:线段树一般是一棵完全二叉树,具有规律性的结构。在数组存储方式下,为了方便表示完全二叉树,需要保证数组的大小是某一层节点数量的上限。对于一棵深度为 k 的完全二叉树,叶子节点数量最多为 2^k,因此数组大小一般设置为 4 * 2^k,以确保足够的空间。

  2. 节点的父子关系:在数组存储方式中,节点 i 的左子节点一般存储在位置 2i,右子节点存储在位置 2i+1。设置数组大小为 4n 可以保证对于任意节点 i,其子节点在数组中的位置都是有效的,不会越界。

  3. 方便计算左右子树位置:在线段树的查询和更新操作中,经常需要根据节点的索引快速定位其左右子树节点。通过设置数组大小为 4n,可以方便地根据节点索引计算出其左右子节点的位置,简化操作。

然后开一个build函数建树,具体操作如下:

  1. 定义数组:首先,需要定义一个大小为 4n 的数组,其中 n 是线段树的叶子节点数量。这个数组将用于存储线段树的节点信息。

  2. 构建线段树:一般将线段树按照完全二叉树的形式存储在数组中。假设根节点在数组中的索引是 1,那么对于节点 i,其左子节点为 2i,右子节点为 2i + 1。

  3. 存储节点信息:每个节点需要保存代表的区间范围和相应的信息,比如区间的最大值、最小值、和等等。在数组中,可以按照某种顺序依次存储这些信息,以便后续的查询和更新操作。

  4. 建立线段树:通过递归或迭代的方式构建线段树。一般会从叶子节点开始向上构建,通过合并子节点的信息得到父节点的信息,直至构建完整的线段树。

  5. 查询和更新:通过线段树的结构和数组存储,可以实现高效的区间查询和更新操作。比如,对于查询一个区间的最大值,可以通过递归向下查询到包含目标区间的节点,并根据存储的信息计算出结果。

  6. 记得注意边界情况:在实现线段树时,需要考虑树的边界情况,比如树的根节点索引是 1,叶子节点索引从 n+1 开始等,以确保正确地访问和操作节点。

build函数建树

void build(LL l, LL r, LL fa) {if (l == r) // //如果左右区间相同,那么必然是叶子节,只有叶子节点是被真实赋值的{t[fa] = a[l];return;}LL mid = (l + r) >> 1;build( l, mid, fa << 1);build(mid + 1, r, fa << 1 | 1);
//使用二分来优化psuh_up(fa);//此处由于我们是要通过子节点来维护父节点,所以push_up的位置应当是在回溯时将子节点的值取和交给父节点
}

二、线段树的区间修改维护

      线段树是一种用于解决区间查询和修改问题的数据结构。在线段树中,区间修改维护指的是在给定一个区间,并修改该区间内所有元素的操作。

  1. 区间修改维护

    • 当需要修改线段树中某个特定区间的值时,可以通过递归的方式向下更新区间。
    • 如果要修改的区间与当前节点表示的区间没有交集,则无需修改该节点。
    • 如果要修改的区间完全包含当前节点的区间,则直接更新当前节点的信息,并将修改操作下传给子节点。
    • 如果要修改的区间与当前节点的区间部分相交,则需要先将当前节点的信息更新,然后将修改操作同时下传给左右子节点。
  2. 区间修改的操作

    • 区间修改的操作通常包括加法、减法、赋值等。
    • 当需要对区间内的每个元素进行相同的修改时,可以利用线段树的特性进行高效操作。
    • 在修改区间时,需要根据当前节点的区间范围、待修改区间和修改方式来确定如何操作当前节点和其子节点。
  3. 递归更新过程

    • 从线段树的根节点开始递归向下更新,直到找到包含待修改区间的叶子节点。
    • 在递归过程中根据节点的区间范围和待修改区间的关系,决定如何更新节点的信息并向下传递修改操作。

       此外,对于区间操作,我们考虑引入一个名叫“ lazy tag ”(懒标记)的东西——之所以称其“lazy”,是因为原本区间修改需要通过先改变叶子节点的值,然后不断地向上递归修改祖先节点直至到达根节点,时间复杂度最高可以到达 O(nlogn) 的级别。但当我们引入了懒标记之后,区间更新的期望复杂度就降到了 O(logn) 的级别且甚至会更低。

因此,我们再弄一个tag数组,大小也是4*N

区间修改update:

void psuh_up(LL fa) {t[fa] = t[fa << 1] + t[fa << 1 | 1];//向上不断维护父节点
}
void push_down(LL l,LL r,LL fa) {LL mid = (l + r) >> 1;t[fa << 1] += tag[fa] * (mid - l + 1);tag[fa << 1] += tag[fa];t[fa << 1|1] += tag[fa] * (r-mid);tag[fa << 1|1] += tag[fa];tag[fa] = 0;// //每次将懒惰标识下放到两个儿子节点,自身置为0,以此不断向下传递 
}
void update(LL ql, LL qr, LL l, LL r, LL k, LL fa) {if (ql <= l && qr >= r) //如果区间被包含,直接返回该节点的懒惰标识{t[fa] +=k * (r - l + 1);tag[fa] += k;return;}LL mid = (l + r) >> 1;push_down(l, r, fa);//下放懒惰标识if (ql <= mid)update(ql, qr, l, mid,k, fa << 1);//朝左边下放if (qr > mid)update(ql, qr, mid + 1, r,k, fa << 1 | 1);//右边psuh_up(fa);//再将修改后的值向上返回,维护父节点
}

三、线段树的区间查询

  1. 区间查询

    • 当需要查询线段树中某个特定区间的信息时,可以通过递归的方式向下查询区间。
    • 如果要查询的区间与当前节点表示的区间没有交集,则无需查询该节点,直接返回默认值(如0或无穷大)。
    • 如果要查询的区间完全包含当前节点的区间,则直接返回该节点存储的信息。
    • 如果要查询的区间与当前节点的区间部分相交,则需要同时查询左右子节点,并根据查询结果合并得到最终结果。
  2. 区间查询的操作

    • 区间查询的操作通常包括求和、求最大值、求最小值等。
    • 在查询区间时,需要根据当前节点的区间范围、待查询区间和查询方式来确定如何操作当前节点和其子节点。
  3. 递归查询过程

    • 从线段树的根节点开始递归向下查询,直到找到包含待查询区间的叶子节点。
    • 在递归过程中根据节点的区间范围和待查询区间的关系,决定如何查询节点的信息并向下传递查询操作。
    • 最终将所有查询结果合并得到最终的区间查询结果。

      通过以上方法,可以实现对线段树中特定区间的查询操作。线段树区间查询是线段树的一个重要功能,能够快速有效地获取区间内的信息,提高了区间查询的效率。

区间查询query:

LL query(LL ql, LL qr, LL l, LL r, LL fa) {LL ret = 0;if (ql <= l && qr >= r) 如果区间被包含,直接返回该节点的懒惰标识{return t[fa];}LL mid = (l + r) >> 1;push_down(l, r, fa);//没有被包含,下放任务if (ql <= mid)ret += query(ql, qr, l, mid, fa << 1);if (qr > mid)ret += query(ql, qr, mid + 1, r, fa << 1|1);//在查询范围的左区间和右区间的值相加并返回return ret;
}
例题:

模板题目:P3372 【模板】线段树 1 - 洛谷

完整代码:
数组实现:
#include<iostream>
using namespace std;
const int N = 1e5 + 10;
typedef long long LL;
LL n, m, t[N * 4], tag[N * 4], a[N];
void psuh_up(LL fa) {t[fa] = t[fa << 1] + t[fa << 1 | 1];//向上不断维护父节点
}
void push_down(LL l,LL r,LL fa) {LL mid = (l + r) >> 1;t[fa << 1] += tag[fa] * (mid - l + 1);tag[fa << 1] += tag[fa];t[fa << 1|1] += tag[fa] * (r-mid);tag[fa << 1|1] += tag[fa];tag[fa] = 0;// //每次将懒惰标识下放到两个儿子节点,自身置为0,以此不断向下传递 
}
LL query(LL ql, LL qr, LL l, LL r, LL fa) {LL ret = 0;if (ql <= l && qr >= r) 如果区间被包含,直接返回该节点的懒惰标识{return t[fa];}LL mid = (l + r) >> 1;push_down(l, r, fa);//没有被包含,下放任务if (ql <= mid)ret += query(ql, qr, l, mid, fa << 1);if (qr > mid)ret += query(ql, qr, mid + 1, r, fa << 1|1);//在查询范围的左区间和右区间的值相加并返回return ret;
}
void update(LL ql, LL qr, LL l, LL r, LL k, LL fa) {if (ql <= l && qr >= r) //如果区间被包含,更新懒惰标识并返回{t[fa] +=k * (r - l + 1);tag[fa] += k;return;}LL mid = (l + r) >> 1;push_down(l, r, fa);//下放懒惰标识if (ql <= mid)update(ql, qr, l, mid,k, fa << 1);//朝左边下放if (qr > mid)update(ql, qr, mid + 1, r,k, fa << 1 | 1);//右边psuh_up(fa);//再将修改后的值向上返回,维护父节点
}
void build(LL l, LL r, LL fa) {if (l == r) // //如果左右区间相同,那么必然是叶子节,只有叶子节点是被真实赋值的{t[fa] = a[l];return;}LL mid = (l + r) >> 1;build(l, mid, fa << 1);build(mid + 1, r, fa << 1 | 1);//使用二分来优化psuh_up(fa);//此处由于我们是要通过子节点来维护父节点,所以push_up的位置应当是在回溯时将子节点的值取和交给父节点
}
int main() {cin >> n >> m;for (int i = 1; i <= n; i++)cin >> a[i];build(1, n, 1);while (m--) {int op; cin >> op;if (op == 1) {LL x, y, k; cin >> x >> y >> k;update(x, y, 1, n, k, 1);}else if(op==2){LL x, y;cin >> x >> y;cout << query(x, y, 1, n, 1) << endl;}}return 0;
}
指针实现:
#include <iostream>
#include <vector>using namespace std;struct Node {int start, end;int sum;Node *left, *right;Node(int start, int end) : start(start), end(end), sum(0), left(nullptr), right(nullptr) {}
};Node* buildSegmentTree(vector<int>& nums, int start, int end) {if (start > end) {return nullptr;}Node* root = new Node(start, end);if (start == end) {root->sum = nums[start];} else {int mid = start + (end - start) / 2;root->left = buildSegmentTree(nums, start, mid);root->right = buildSegmentTree(nums, mid + 1, end);root->sum = root->left->sum + root->right->sum;}return root;
}int query(Node* root, int qs, int qe) {if (root == nullptr || qs > root->end || qe < root->start) {return 0;} else if (qs <= root->start && qe >= root->end) {return root->sum;} else {return query(root->left, qs, qe) + query(root->right, qs, qe);}
}int main() {vector<int> nums = {1, 3, 5, 7, 9, 11};Node* root = buildSegmentTree(nums, 0, nums.size() - 1);cout << "Sum of elements in range [2, 4]: " << query(root, 2, 4) << endl;return 0;
}


总结

本文关于线段树的讲解就到这里,有什么疑问或者有什么错误的地方欢迎一起交流学习

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

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

相关文章

Jenkins 2.492.2 LTS 重置管理员密码

文章目录 1. Jenkins 关闭用户认证2. jenkins 修改密码 如果忘记了 Jenkins 的管理员密码的话&#xff0c;也不用担心&#xff0c;只要你有权限访问 Jenkins 的根目录&#xff0c;就可以轻松地重置密码。 1. Jenkins 关闭用户认证 // 查看 jenkins 家目录&#xff08;使用 doc…

《AI大模型应知应会100篇》第26篇:Chain-of-Thought:引导大模型进行步骤推理

第26篇&#xff1a;Chain-of-Thought&#xff1a;引导大模型进行步骤推理 摘要 在自然语言处理&#xff08;NLP&#xff09;和人工智能领域&#xff0c;如何让大模型像人类一样进行逐步推理是一个核心挑战。Chain-of-Thought (思维链) 技术的出现为这一问题提供了强有力的解决…

SICAR 标准 安全门锁操作箱 按钮和指示灯说明

1、安全门锁操作箱 2、按钮和指示灯说明 一、指示灯说明 红灯&#xff1a; 常亮&#xff1a;表示安全门已解锁&#xff1b;闪烁&#xff1a;表示安全门未复位&#xff1b;熄灭&#xff1a;表示安全门已复位。 黄灯&#xff1a; 常亮&#xff1a;表示处于维修模式。 绿灯&…

MAC-​​需求​​:10万订单异步执行库存扣减、短信通知。

批量任务并行处理​​ 实现,通过拆分任务、异步执行和线程池管理提升处理。 ​​10万订单异步处理方案设计​​ 基于图中代码的批量处理框架,结合订单业务需求,以下是 ​​库存扣减与短信通知的异步实现​​: ​​1. 代码实现(基于原有框架改造)​​ @Service public…

python 库 下载 ,整合在一个小程序 UIUIUI

上图 import os import time import threading import requests import subprocess import importlib import tkinter as tk from tkinter import ttk, messagebox, scrolledtext from concurrent.futures import ThreadPoolExecutor, as_completed from urllib.parse import…

Flutter与FastAPI的OSS系统实现

作者&#xff1a;孙嘉成 目录 一、对象存储 二、FastAPI与对象存储 2.1 缤纷云S4服务API对接与鉴权实现 2.2 RESTful接口设计与异步路由优化 三、Flutter界面与数据交互开发 3.1 应用的创建 3.2页面的搭建 3.3 文件的上传 关键词&#xff1a;对象存储、FastAPI、Flutte…

洛谷P3373线段树详解【模板】

洛谷P3373题目概述 洛谷P3373是一道关于线段树的模板题&#xff0c;题目名称为“【模板】线段树 2”。题目的主要要求是对一个长度为 n 的数列进行如下操作&#xff1a; 将某区间每个数乘上一个数。将某区间每个数加上一个数。求出某区间所有数的和。 线段树简介 线段树是一…

【计算机视觉】CV实战项目- COVID 社交距离检测(covid-social-distancing-detection)

COVID 社交距离检测&#xff08;covid-social-distancing-detection&#xff09; 一、项目概述二、项目架构三、环境搭建四、运行项目五、输出结果六、常见问题及解决方法报错1. cv2.error: OpenCV(4.11.0) :-1: error: (-5:Bad argument) in function circle报错2 cv2.circle(…

CMake使用教程

一、CMake 简介 CMake 是一个跨平台的构建工具&#xff0c;用于自动化生成不同平台&#xff08;如 Makefile、Visual Studio、Xcode 等&#xff09;的构建文件。它的核心是编写 CMakeLists.txt 文件&#xff0c;定义项目的构建规则。 二、安装 CMake Linux: sudo apt-get ins…

大模型Rag - 两大检索技术

一、稀疏检索&#xff1a;关键词匹配的经典代表 稀疏检索是一种基于关键词统计的传统检索方法。其基本思想是&#xff1a;通过词频和文档频率来衡量一个文档与查询的相关性。 核心原理 文档和查询都被表示为稀疏向量&#xff08;如词袋模型&#xff09;&#xff0c;只有在词…

LNA设计

设计目的 为后级提供足够的增益以克服后级电路噪声 尽可能小的噪声和信号失真 确保输入和输出端的阻抗匹配 确保信号线性度 评价标准 噪声系数 功率增益 工作频率和带宽 输入信号功率动态范围 端口电压驻波比 稳定性 基于SP模型的LNA设计 直流分析 S参数分析 设计指标 &#xf…

Vue 常见组件及使用方式全解析

一、引言 在 Vue 开发中&#xff0c;组件是构建复杂用户界面的基石。通过使用各种常见组件&#xff0c;我们可以快速搭建出功能丰富、交互性强的应用程序。本文将详细介绍 Vue 开发中一些常见组件及其使用方式。 二、基础 UI 组件 &#xff08;一&#xff09;按钮组件&#…

设计测试用例模板

面试时问你一个场景&#xff0c;要你设计测试用例&#xff0c;你会怎么回答&#xff1f; 面试官让你设计一个功能的测试用例&#xff0c;比如“上传文件功能”&#xff0c;其实就是想考你&#xff1a; 思维是否全面能不能抓住重点会不会分类和使用测试方法有没有考虑异常情况…

Git 解决“Filename too long”问题

在 Windows 系统中使用 Git 时&#xff0c;遇到 Filename too long 错误通常是由于系统默认的路径长度限制&#xff08;260 字符&#xff09;导致的。以下是综合多种场景的解决方案&#xff1a; 一、快速解决方法 启用 Git 长路径支持 通过 Git 配置命令允许处理超长文件名&am…

Spring Boot 3 + SpringDoc:打造接口文档

1、背景公司 新项目使用SpringBoot3.0以上构建&#xff0c;其中需要对外输出接口文档。接口文档一方面给到前端调试&#xff0c;另一方面给到测试使用。 2、SpringDoc 是什么&#xff1f; SpringDoc 是一个基于 Spring Boot 项目的库&#xff0c;能够自动根据项目中的配置、…

Swagger2Refit

把swagger相关接口转成refit格式&#xff0c;以便其他服务调用 使用工具Refitter. Refitter 项目使用教程 Refit Client API Generator for OpenAPI 项目地址: github.com GitCode - 全球开发者的开源社区,开源代码托管平台 安装 Refitter CLI 工具 首先&#xff0c;通过…

【java 13天进阶Day05】数据结构,List,Set ,TreeSet集合,Collections工具类

常见的数据结构种类 集合是基于数据结构做出来的&#xff0c;不同的集合底层会采用不同的数据结构。不同的数据结构&#xff0c;功能和作用是不一样的。数据结构&#xff1a; 数据结构指的是数据以什么方式组织在一起。不同的数据结构&#xff0c;增删查的性能是不一样的。不同…

systemctl管理指令

今天我们来继续学习服务管理指令,接下来才是重头戏-systemctl,那么话不多说,直接开始吧. systemctl管理指令 1.基本语法: systemctl [start | stop | restart | status]服务 注&#xff1a;systemctl指令管理的服务在/usr/lib/ systemd/system查看 2.systemctl设置服务的自…

STM32单片机教程:从零开始打造智能天气时钟

STM32单片机教程&#xff1a;从零开始打造智能天气时钟 大家好&#xff01;今天我想为大家详细介绍一下我们的STM32课程&#xff0c;以及如何从零基础逐步掌握单片机开发技能&#xff0c;最终实现一个完整的智能天气时钟项目。 课程面向人群 本课程主要面向那些已经通过野火…

Neovim插件深度解析:mcphub.nvim如何用MCP协议重构开发体验

在AI与工具链深度融合的今天,Neovim 作为现代开发者的生产力工具,正通过插件生态不断突破边界。mcphub.nvim 作为一款基于 MCP(Model Context Protocol) 协议的插件,重新定义了Neovim与智能工具的交互方式。它不仅简化了MCP服务器的集成与管理,更通过直观的UI和生态整合,…