线段树详解 原理解释 + 构建步骤 + 代码(带模板)

目录

介绍:    

定义:

以具体一个题目为例:​

树的表示方法:

实现步骤:

构建结点属性:

pushup函数:

build函数:

pushdown函数:

modify函数:

query函数:

如何记忆:

模板:


介绍:    

        线段树(Segment Tree)是一种常用的数据结构,用于解决涉及区间查询的问题。它主要用于在数组或列表等数据结构上支持以下两类查询操作:

  1. 区间查询:查询某个区间内的统计信息,例如求和、最大值、最小值等。
  2. 区间更新:修改数组中某个区间元素的值,并相应地更新线段树中的信息。

        核心思想是将原始数据递归地划分成一系列不相交的区间,并在每个区间上维护一些预先计算好的信息,以支持高效的区间查询。

定义:

        假设我们有一个包含 N 个元素的数组 A,线段树 T 是基于数组 A 的线段树。线段树 T 是一个满二叉树,它具有以下性质:

  1. 根节点表示整个数组的区间 [1, N]。
  2. 如果一个节点表示的区间是 [left, right],则它的左子节点表示的区间是 [left, mid],右子节点表示的区间是 [mid+1, right],其中 mid 是 left 和 right 的中间值。
  3. 叶子节点表示数组 A 中的单个元素,而内部节点表示对应区间上的预计算信息(如区间和、区间最大值等)。
  4. 线段树通常使用数组来模拟实现。

线段树算法一般包含以下个函数:

        1.build(); 初始构建一个线段树。

        2.pushpu(); 向上传递信息。

        3.pushdown(); 向下传递懒标记,并且更新子树。

        4.modify(); 修改某一区间。

        5.query(); 查询某一区间信息。

下面我们一个一个来介绍。 

以具体一个题目为例:

下面解析以此题目为例子。

树的表示方法:

        我们用 tr 数组来模拟这颗树。

        假设根节点在 tr 数组中的的下标为为 i,那么其左右子树的下标为:

                左:i * 2 (i << 1)

                右:i * 2 + 1 (i << 1 | 1)

        我们一般使用位运算,也就是括号里的,含义是一样的。所以可以计算出,tr 数组的长度最多就是题目所给数组长度的4倍。

实现步骤:

事先把输入的数组存在 w数组 中。

构建结点属性:

        树结点其实就是一个区间,所以属性包含:左右边界,懒标记。

        此题的懒标记就是区间需要加上的值 d 。

        根据题目我们还需要查询区间的元素和,所以在其中添加一个 sum。

struct Node
{int l, r;LL sum;LL add; // 懒标记
}tr[N * 4];;

pushup函数:

        我们在 build 一颗树之前,要先写 pushup 函数,用于向上传递信息,因为我们只知道叶子结点的值,我们要用后序遍历去构建父亲,所以要用到 pushup ,根据题目,我们要向上传递的信息显然是左右子树的 sum 和,这样就可以算出父亲的 sum 。

void pushup(int u) // 向上传递信息
{tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}

build函数:

        接下来我们开始构建这颗树,若区间内只有一个元素(l == r),说明我们找到了叶子结点,给叶子结点赋值,若不是结子节点(l != r),就继续向左右子树递归,在递归完成时(后序遍历)使用pushup,通过已经获得值的子树去更新父亲。

void build(int u, int l, int r)
{if (l == r) tr[u] = {l, r, w[l], 0}; // 叶子节点else{tr[u] = {l, r};int mid = l + r >> 1;build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r); // 若不是叶子节点,向下递归pushup(u); // 通过子树构建父亲}
}

pushdown函数:

        pushdown函数是给子树传递懒标记的,如果懒标记不为空,就将父亲的懒标记传递给左右子树,并且通过懒标记更新左右子树的信息,此题求的是sum,所以子树的 sum 值就要加上区间长度乘上父亲的懒标记,最后清空父亲的懒标记。

        注意:懒标记表示的子树需要添加的信息,不包含父亲自己,所以在传递懒标记时,才要传递懒标记同时更新子树。

void pushdown(int u)
{auto &root = tr[u], &left = tr[u << 1], & right = tr[u << 1 | 1];if (root.add){// 传递懒标记并且更新子树left.add += root.add, left.sum += (LL)(left.r - left.l + 1) * root.add;right.add += root.add, right.sum += (LL)(right.r - right.l + 1) * root.add;root.add = 0; // 删除懒标记}
}

modify函数:

        修改区间信息,如果当前遍历的结点区间已经在区间中,那么就直接给其加上懒标记,并且计算更新其 sum。如果当前遍历的结点区间中的一部分是需要修改的区间,那么就先向下传递懒标记pushdown,然后在向需要修改的左右子树去递归,后序返回时,要给更新父亲pushup。

void modify(int u, int l, int r, int v)
{// 结点在要修改的区间中if (l <= tr[u].l && r >= tr[u].r){tr[u].sum += (tr[u].r - tr[u].l + 1) * v;tr[u].add += v; // 加上懒标记}else{pushdown(u); // 先传递懒标记int mid = tr[u].l + tr[u].r >> 1;if (l <= mid) modify(u << 1, l, r, v);if (r > mid) modify(u << 1 | 1, l, r, v);pushup(u); // 更新父亲}
}

query函数:

        用于查询区间信息,这里就是查询区间的sum。若遍历到的结点区间在查询区间之中,就返回其sum,若结点区间只有一部分在查询区间中,一样的,也是先传递懒标记,然后继续向需要计算的左右子树去递归,后序返回时计算结果。

LL query(int u, int l, int r)
{if (l <= tr[u].l && r >= tr[u].r) return tr[u].sum; // 返回区间信息pushdown(u); // 也是先传递懒标记LL v = 0;int mid = tr[u].l + tr[u].r >> 1;if (l <= mid) v = query(u << 1, l, r);if (r > mid) v += query(u << 1 | 1, l, r);return v;
}

如何记忆:

        最重要的是注意每个函数pushup,pushdown函数的位置。只有在modify函数才两个一起用。

build函数只用一个pushup,query函数只用一个pushdown。

模板:

根据具体题目,自行修改。

// 操作是给区间每一个数加d
// 询问是求某一区间和
#include<iostream>using namespace std;typedef long long LL;
const int N = 100010;int w[N];
int n, m;struct Node
{int l, r;LL sum;LL add; // 懒标记
}tr[N * 4];;void pushup(int u) // 向上传递信息
{tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}
void pushdown(int u)
{auto &root = tr[u], &left = tr[u << 1], & right = tr[u << 1 | 1];if (root.add){// 传递懒标记并且更新子树left.add += root.add, left.sum += (LL)(left.r - left.l + 1) * root.add;right.add += root.add, right.sum += (LL)(right.r - right.l + 1) * root.add;root.add = 0; // 删除懒标记}
}
void build(int u, int l, int r)
{if (l == r) tr[u] = {l, r, w[l], 0}; // 叶子节点else{tr[u] = {l, r};int mid = l + r >> 1;build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r); // 若不是叶子节点,向下递归pushup(u); // 通过子树构建父亲}
}
void modify(int u, int l, int r, int v)
{// 结点在要修改的区间中if (l <= tr[u].l && r >= tr[u].r){tr[u].sum += (tr[u].r - tr[u].l + 1) * v;tr[u].add += v; // 加上懒标记}else{pushdown(u); // 先传递懒标记int mid = tr[u].l + tr[u].r >> 1;if (l <= mid) modify(u << 1, l, r, v);if (r > mid) modify(u << 1 | 1, l, r, v);pushup(u); // 更新父亲}
}LL query(int u, int l, int r)
{if (l <= tr[u].l && r >= tr[u].r) return tr[u].sum; // 返回区间信息pushdown(u); // 也是先传递懒标记LL v = 0;int mid = tr[u].l + tr[u].r >> 1;if (l <= mid) v = query(u << 1, l, r);if (r > mid) v += query(u << 1 | 1, l, r);return v;
}int main()
{scanf("%d%d", &n, &m);for (int i = 1; i <= n; ++i) scanf("%d", &w[i]); // 读入数组build(1, 1, n); // 以1为根节点,1~n区间建树char op[2];int l, r, t;// 读入修改和查询,q是查询,否则是修改while (m -- ){scanf("%s%d%d", op, &l, &r);if (*op == 'Q') printf("%lld\n", query(1, l, r));else{scanf("%d", &t);modify(1, l, r, t);}}return 0;
}

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

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

相关文章

『赠书活动 | 第十六期』《深入浅出Java虚拟机:JVM原理与实战》

&#x1f497;wei_shuo的个人主页 &#x1f4ab;wei_shuo的学习社区 &#x1f310;Hello World &#xff01; 『赠书活动 &#xff5c; 第十六期』 本期书籍&#xff1a;《深入浅出Java虚拟机&#xff1a;JVM原理与实战》 赠书规则&#xff1a;评论区&#xff1a;点赞&#xff…

站点可靠性工程 (SRE)

随着世界各地的组织努力开发安全、可靠、可扩展且可持续的 IT 基础架构&#xff0c;对高效基础架构监控和管理的需求日益增长&#xff0c;企业正在用不可扩展的遗留架构换取现代解决方案&#xff0c;在尖端技术的推动下&#xff0c;这些使基础设施管理过程更加顺畅和轻松&#…

SpringBoot + minio实现分片上传、秒传、续传

什么是minio MinIO是一个基于Go实现的高性能、兼容S3协议的对象存储。它采用GNU AGPL v3开源协议&#xff0c;项目地址是https://github.com/minio/minio。 引用官网&#xff1a; MinIO是根据GNU Affero通用公共许可证v3.0发布的高性能对象存储。它与Amazon S3云存储服务兼容…

C++设计模式之访问者模式

C访问者设计模式 文章目录 C访问者设计模式什么是设计模式什么是访问者设计模式该模式有什么优缺点优点缺点 如何使用 什么是设计模式 设计模式是一种通用的解决方案&#xff0c;用于解决特定的一类问题。它是一种经过验证的代码组织方式&#xff0c;可以帮助开发人员更快地实…

Linux——平台设备及其驱动

目录 前言 一、平台设备 二、平台驱动 三、平台驱动简单实例 四、 电源管理 五、udev 和驱动的自动加载 六、使用平台设备的LED 驱动 七、自动创建设备节点 前言 要满足 Linux 设备模型&#xff0c;就必须有总线、设备和驱动。但是有的设备并没有对应的物理总线&#x…

互联网被裁的程序员,未来有什么方向呢?

做了一份程序员“失业”后的自救方向汇总&#xff1a; 接下来挨个聊聊。 产品经理 都说产品和技术总是相爱相杀&#xff0c;不过产品和技术的关系也是最近的。 无论是产品转技术&#xff0c;还是技术转产品&#xff0c;相对来说都是比较容易的&#xff0c;很多底层逻辑是互通…

后端通过CorsRegistry对象配置了全局跨域,但是前端仍然报CORS跨域错误

后端通过CorsRegistry配置了全局跨域&#xff0c;但是前端仍然报CORS跨域错误 问题背景 在实现登录功能时&#xff0c;我先是通过CorsRegistry配置了全局跨域&#xff0c;然后配置了一个登录拦截器后前端就报错CORS跨域错误 问题原因 前置知识 首先我们来了解一下什么是跨域错误…

无脑入门pytorch系列(一)—— nn.embedding

本系列教程适用于没有任何pytorch的同学&#xff08;简单的python语法还是要的&#xff09;&#xff0c;从代码的表层出发挖掘代码的深层含义&#xff0c;理解具体的意思和内涵。pytorch的很多函数看着非常简单&#xff0c;但是其中包含了很多内容&#xff0c;不了解其中的意思…

网络安全策略应包含哪些?

网络安全策略是保护组织免受网络威胁的关键措施。良好的网络安全策略可以确保数据和系统的保密性、完整性和可用性。以下是一个典型的网络安全策略应包含的几个重要方面&#xff1a; 1. 强化密码策略&#xff1a;采用强密码&#xff0c;要求定期更换密码&#xff0c;并使用多因…

Java类集框架(一)

目录 1.Collection集合接口 2.List 接口 (常用子类 ArrayList ,LinkedList,Vector) 3.Set 集合 接口(常用子类 HashSet LinkedHashSet,TreeSet) 4.集合输出(iterator , Enumeration) 1.Collection集合接口 Collection是集合中最大父接口&#xff0c;在接口中定义了核心的…

vue中各种混淆用法汇总

✨在生成、导出、导入、使用 Vue 组件的时候&#xff0c;像我这种新手就会常常被位于不同文件的 new Vue() 、 export default{} 搞得晕头转向。本文对常见用法汇总区分 new Vue() &#x1f4a6;Vue()就是一个构造函数&#xff0c;new Vue()是创建一个 vue 实例。该实例是一个…

Redis - 缓存的双写一致性

概念&#xff1a; 当修改了数据库的数据也要同时更新缓存的数据&#xff0c;缓存和数据库的数据要保持一致 那为什么会有不一致的情况呢&#xff1f; 如果不追求一致性&#xff0c;正常有两种做法 先修改数据库 后删除旧的缓存先删除旧的缓存 再修改数据库 我们以先删除旧的…

html学习9(脚本)

1、<script>标签用于定义客户端脚本&#xff0c;比如JavaScript&#xff0c;既可包含脚本语句&#xff0c;也可通过src属性指向外部文件。 2、JavaScript最常用于图片操作、表单验证及内容动图更新。 3、<noscript>标签用于在浏览器禁用脚本或浏览器不支持脚本&a…

华为数通HCIP-PIM原理与配置

组播网络概念 组播网络由组播源&#xff0c;组播组成员与组播路由器组成。 组播源的主要作用是发送组播数据。 组播组成员的主要作用是接收组播数据&#xff0c;因此需要通过IGMP让组播网络感知组成员位置与加组信息。 组播路由器的主要作用是将数据从组播源发送到组播组成员。…

第七篇:k8s集群使用helm3安装Prometheus Operator

安装Prometheus Operator 目前网上主要有两种安装方式&#xff0c;分别为&#xff1a;1. 使用kubectl基于manifest进行安装 2. 基于helm3进行安装。第一种方式比较繁琐&#xff0c;需要手动配置yaml文件&#xff0c;特别是需要配置pvc相关内容时&#xff0c;涉及到的yaml文件太…

软件测试面试真题 | 什么是PO设计模式?

面试官问&#xff1a;UI自动化测试中有使用过设计模式吗&#xff1f;了解什么是PO设计模式吗&#xff1f; 考察点 《page object 设计模式》&#xff1a;PageObject设计模式的设计思想、设计原则 《web自动化测试实战》&#xff1a;结合PageObject在真实项目中的实践与应用情…

Shell脚本学习-MySQL单实例和多实例启动脚本

已知MySQL多实例启动命令为&#xff1a; mysqld_safe --defaults-file/data/3306/my.cnf & 停止命令为&#xff1a; mysqladmin -uroot -pchang123 -S /data/3306/mysql.sock shutdown 请完成mysql多实例的启动脚本的编写&#xff1a; 问题分析&#xff1a; 要想写出脚…

mybatis-plus 用法

目录 1 快速开始 1.1 依赖准备 1.2 配置准备 1.3 启动服务 2 使用 2.1 实体类注解 2.2 CRUD 2.3 分页 2.4 逻辑删除配置 2.5 通用枚举配置 2.6 自动填充 2.7 多数据源 3 测试 本文主要介绍 mybatis-plus 这款插件&#xff0c;针对 springboot 用户。包括引入&…

Redis 高可用:主从复制、哨兵模式、集群模式

文章目录 一、redis高可用性概述二、主从复制2.1 主从复制2.2 数据同步的方式2.2.1 全量数据同步2.2.2 增量数据同步 2.3 实现原理2.3.1 服务器 RUN ID2.3.2 复制偏移量 offset2.3.3 环形缓冲区 三、哨兵模式3.1 原理3.2 配置3.3 流程3.4 使用3.5 缺点 四、cluster集群4.1 原理…

带头单链表,附带完整测试程序

&#x1f354;链表基础知识 1.概念&#xff1a;链表是由多个节点链接构成的&#xff0c;节点包含数据域和指针域&#xff0c;指针域上存放的指针指向下一个节点 2.链表的种类&#xff1a;按单向或双向、带头或不带头、循环或不循环分为多个种类 3.特点&#xff1a;无法直接找到…