【数据结构】线段树

目录

  • 1.概述
  • 2.代码实现
    • 2.1.聚合操作——求和
    • 2.2.聚合操作——求和、求最小值、求最大值
  • 3.应用
  • 4.与前缀和之间的区别

更多数据结构与算法的相关知识可以查看数据结构与算法这一专栏。

1.概述

(1)线段树 (Segment Tree) 是一种二叉树形数据结构,经常用于高效地处理一维区间的各种查询和修改问题。

(2)一个线段树通常对应于一个区间,每个节点表示一个区间,具体如下图所示。

  • 对于线段树中的每个节点,它有一个区间范围和一个值。
  • 叶节点表示区间中的单个元素,而非叶子节点表示区间中的所有元素。
  • 线段树的每个节点表示区间的一部分,其左子树表示左半部分区间,右子树表示右半部分区间。因此,线段树的叶节点数总是等于数据元素的个数,而线段树的高度为 ⌈logn⌉ + 1,其中 n 为元素总个数。

在这里插入图片描述

① 上图来自线段树_百度百科。
② 一般来说,在代码中会用数组来存储某个区间内的元素,该数组内的元素可以是无序或者有序的,例如,nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 或者 nums = [2, 4, -1, 0, 9] 等。上图中线段树中的区间正好是前一个数组。

(3)线段树的主要优势是能够在 O(logn) 时间复杂度内执行区间查询(如最大值、最小值、区间和等)和区间修改操作(如区间加、区间减等),因此它非常适合解决那些需要频繁区间查询和修改的问题。

2.代码实现

(1)在线段树中,区间的聚合值是指该区间内元素的某种聚合操作的结果。这个聚合操作可以是求和求最小值求最大值等。聚合值的具体含义取决于所解决的问题,本节中分别给出以下两种情况。

(2)线段树的构建过程与 108.将有序数组转换为二叉搜索树这题类似,具体如下:

  • 定义线段树节点:线段树是一种二叉树,每个节点代表一个区间。每个节点包含了该区间的起始点start、结束点end,以及其他你可能需要的附加信息。
  • 定义递归构建函数:创建一个递归函数来构建线段树。该函数接收输入参数为当前节点、当前区间的起始点和结束点。
  • 基本情况处理:对于当前节点,如果起始点和结束点相等,表示当前节点为叶子节点,直接返回。
  • 划分区间:计算当前区间的中点 mid,将区间分割成两个子区间。通常是将区间一分为二,可以选择将 mid 设置为 (start+end)/2。
  • 递归构建左子树和右子树:调用递归函数,传入左子树和右子树的起始点和中点以构建左右子树。
  • 合并信息:在递归回溯时,将左右子树的信息合并到当前节点。这通常取决于你的问题需求,可以是求和、求最大值、求最小值等。
  • 返回根节点:递归构建完成后,返回根节点。

2.1.聚合操作——求和

(1)实现区间求和操作(包括修改区间的某个元素)的代码实现如下:

class SegmentTree {//线段树数组,segmentTree[i] 表示线段树的第 i 个节点(区间)的聚合值,本代码中是区间和int[] segmentTree;//原始数组int[] nums;public SegmentTree(int[] nums) {this.nums = nums;int n = nums.length;//确定树的高度int height = (int) (Math.ceil(Math.log(n) / Math.log(2))) + 1;//根据树的高度计算需要的线段树数组大小int maxSize = (int) Math.pow(2, height) - 1;//创建线段树数组segmentTree = new int[maxSize];//构建线段树buildTree(0, 0, n - 1);}//构建线段树private int buildTree(int index, int start, int end) {//叶子节点if (start == end) {//叶子节点存储对应的原始数组值segmentTree[index] = nums[start];return segmentTree[index];}int mid = start + (end - start) / 2; // 计算中间位置//分别递归构建左子树和右子树segmentTree[index] = buildTree(2 * index + 1, start, mid) +buildTree(2 * index + 2, mid + 1, end);return segmentTree[index];}//更新原始数组中的某个元素,并同时更新线段树public void update(int i, int val) {//计算变化的差值int diff = val - nums[i];//更新原始数组中的值nums[i] = val;//更新线段树updateTree(0, 0, nums.length - 1, i, diff);}//更新线段树private void updateTree(int index, int start, int end, int i, int diff) {if (i < start || i > end) {//该节点不包含要更新的元素,直接返回return;}//更新当前节点的值segmentTree[index] += diff;if (start != end) {//计算中间位置int mid = start + (end - start) / 2;//递归更新左子树updateTree(2 * index + 1, start, mid, i, diff);//递归更新右子树updateTree(2 * index + 2, mid + 1, end, i, diff);}}//查询线段树中某个区间的和public int querySum(int left, int right) {return queryTree(0, 0, nums.length - 1, left, right);}// 查询线段树private int queryTree(int index, int start, int end, int left, int right) {if (left > end || right < start) {//区间不相交,返回 0return 0;}if (left <= start && right >= end) {//当前节点表示的区间完全被查询区间包含,直接返回当前节点的值return segmentTree[index];}//计算中间位置int mid = start + (end - start) / 2;//分别递归查询左子树和右子树return queryTree(2 * index + 1, start, mid, left, right) +queryTree(2 * index + 2, mid + 1, end, left, right);}
}

(2)测试代码如下:

class SegmentTreeTest {public static void main(String[] args) {//原始数组,可以是有序或者无序的int[] nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};SegmentTree segmentTree = new SegmentTree(nums);//查询区间 [1, 4] 的和,即 nums[1...4] 的和int sum = segmentTree.querySum(1, 4);System.out.println("Sum of range [1, 4]: " + sum);//将数组下标为 2 的元素更新为 6,即更新 nums[2] = 6,同时更新线段树segmentTree.update(2, 6);//再次查询区间 [1, 4] 的和sum = segmentTree.querySum(1, 4);System.out.println("Updated sum of range [1, 4]: " + sum);}
}

输出结果如下:

Sum of range [1, 4]: 14
Updated sum of range [1, 4]: 17

2.2.聚合操作——求和、求最小值、求最大值

(1)实现区间求和、求最小值、求最大值操作(包括修改区间的某个元素)的代码实现如下:

class SegmentTree {private Node root;//定义节点类,用于表示某个区间private class Node {int start;int end;int sum;int max;int min;Node left;Node right;Node(int start, int end) {this.start = start;this.end = end;this.sum = 0;this.max = Integer.MIN_VALUE;this.min = Integer.MAX_VALUE;}}public SegmentTree(int[] nums) {this.root = build(nums, 0, nums.length - 1);}//构建线段树private Node build(int[] nums, int start, int end) {if (start > end) {return null;}Node node = new Node(start, end);if (start == end) {node.sum = nums[start];node.max = nums[start];node.min = nums[start];} else {int mid = start + (end - start) / 2;node.left = build(nums, start, mid);node.right = build(nums, mid + 1, end);node.sum = node.left.sum + node.right.sum;node.max = Math.max(node.left.max, node.right.max);node.min = Math.min(node.left.min, node.right.min);}return node;}//查询线段树中某个区间的和public int queryRangeSum(int start, int end) {return queryRangeSum(root, start, end);}private int queryRangeSum(Node node, int start, int end) {if (node.start == start && node.end == end) {return node.sum;}int mid = node.start + (node.end - node.start) / 2;if (end <= mid) {return queryRangeSum(node.left, start, end);} else if (start > mid) {return queryRangeSum(node.right, start, end);} else {return queryRangeSum(node.left, start, mid) + queryRangeSum(node.right, mid + 1, end);}}//查询线段树中某个区间的最大值public int queryRangeMax(int start, int end) {return queryRangeMax(root, start, end);}private int queryRangeMax(Node node, int start, int end) {if (node.start == start && node.end == end) {return node.max;}int mid = node.start + (node.end - node.start) / 2;if (end <= mid) {return queryRangeMax(node.left, start, end);} else if (start > mid) {return queryRangeMax(node.right, start, end);} else {return Math.max(queryRangeMax(node.left, start, mid),queryRangeMax(node.right, mid + 1, end));}}//查询线段树中某个区间的最小值public int queryRangeMin(int start, int end) {return queryRangeMin(root, start, end);}private int queryRangeMin(Node node, int start, int end) {if (node.start == start && node.end == end) {return node.min;}int mid = node.start + (node.end - node.start) / 2;if (end <= mid) {return queryRangeMin(node.left, start, end);} else if (start > mid) {return queryRangeMin(node.right, start, end);} else {return Math.min(queryRangeMin(node.left, start, mid),queryRangeMin(node.right, mid + 1, end));}}//更新原始数组中的某个元素,并同时更新线段树public void update(int index, int value) {update(root, index, value);}private void update(Node node, int index, int value) {if (node.start == node.end) {node.sum = value;node.max = value;node.min = value;return;}int mid = node.start + (node.end - node.start) / 2;if (index <= mid) {update(node.left, index, value);} else {update(node.right, index, value);}node.sum = node.left.sum + node.right.sum;node.max = Math.max(node.left.max, node.right.max);node.min = Math.min(node.left.min, node.right.min);}
}

(2)测试代码如下:

class SegmentTreeTest {public static void main(String[] args) {//原始数组,可以是有序或者无序的int[] nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};SegmentTree segmentTree = new SegmentTree(nums);//查询区间 [1, 4] 的和,即 nums[1...4] 的和int sum = segmentTree.queryRangeSum(1, 4);System.out.println("Sum of range [1, 4]: " + sum);int max = segmentTree.queryRangeMax(1, 4);System.out.println("Max of range [1, 4]: " + max);int min = segmentTree.queryRangeMin(1, 4);System.out.println("Min of range [1, 4]: " + min);//将数组下标为 1 的元素更新为 0,即更新 nums[1] = 0,同时更新线段树segmentTree.update(1, 0);//将数组下标为 2 的元素更新为 6,即更新 nums[2] = 6,同时更新线段树segmentTree.update(2, 6);//再次查询区间 [1, 4] 的和sum = segmentTree.queryRangeSum(1, 4);System.out.println("Updated sum of range [1, 4]: " + sum);max = segmentTree.queryRangeMax(1, 4);System.out.println("Updated Sum of range [1, 4]: " + max);min = segmentTree.queryRangeMin(1, 4);System.out.println("Updated Min of range [1, 4]: " + min);}
}

输出结果如下:

Sum of range [1, 4]: 14
Max of range [1, 4]: 5
Min of range [1, 4]: 2
Updated sum of range [1, 4]: 15
Updated Sum of range [1, 4]: 6
Updated Min of range [1, 4]: 0

3.应用

(1)LeetCode 中的 307.区域和检索 - 数组可修改这题便是对线段树的具体应用,其题目如下。显然,使用上面的代码可以直接求解。

在这里插入图片描述

(2)大家可以去 LeetCode 上找相关的线段树的题目来练习,或者也可以直接查看 LeetCode 算法刷题目录 (Java) 这篇文章中的线段树章节。如果大家发现文章中的错误之处,可在评论区中指出。

4.与前缀和之间的区别

(1)线段树和前缀和是两种常见的用于解决区间查询问题的数据结构,它们有一些区别:

  • 数据结构
    • 线段树是一种二叉树结构,用于处理区间查询和更新操作。它将区间划分为不相交的子区间,并将每个子区间的信息存储在相应节点中。
    • 前缀和是一个数组,用于存储前缀和值。它通过计算数组元素累加和的方式存储数据。
  • 功能
    • 线段树可以支持多种区间查询操作,例如区间和、区间最大值、区间最小值等。它可以在 O(logN) 的时间复杂度内完成查询和更新操作。
    • 前缀和主要用于计算数组中特定区间的和。它可以在 O(1) 的时间内计算出给定区间的和,但只能处理区间和的查询。
  • 空间复杂度
    • 线段树的空间复杂度为 O(N),其中 N 是数组的大小。它需要存储整个线段树的节点。
    • 前缀和的空间复杂度为 O(N),其中 N 是数组的大小。它只需要存储一个与数组大小相等的前缀和数组。
  • 应用场景
    • 线段树通常用于解决需要频繁进行区间查询和更新操作的问题,比如计算数组的区间和、区间最大值和最小值等。
    • 前缀和通常用于解决需要频繁计算数组特定区间和的问题,比如计算子数组的和、快速判断数组中是否存在某个区间的和等。

(2)综上所述,线段树和前缀和在功能和应用场景上略有不同,选择使用哪种数据结构取决于具体的问题需求和效率要求。

有关前缀和的相关知识可以参考【数据结构】前缀和数组这篇文章。

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

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

相关文章

C#使用WebSocket进行链接

C#使用WebSocket进行网络链接&#xff0c;和服务端搭建一个长连接进行通信。 有两种方式&#xff1a;串口方式&#xff08;SerialPort&#xff09;和网口方式&#xff08;Socket 包括&#xff1a;TcpClient和UdpClient&#xff09; 准备&#xff1a; 1.C#使用WebSocket的一种…

计算机网络:网络层

0 本节主要内容 问题描述 解决思路 1 问题描述 两大问题&#xff08;重点&#xff0c;也是难点&#xff09;&#xff1a; 地址管理&#xff1b;路由选择。 1.1 子问题1&#xff1a;地址管理 网络上的这些主机和节点都需要使用一种规则来区分&#xff0c;就相当于是一种身…

Docker和Kubernetes:区别与优势对比

在现代软件开发和部署中&#xff0c;Docker和Kubernetes是两个备受关注的技术。本文将对Docker和Kubernetes进行比较&#xff0c;探讨它们的区别和各自的优势。 引言 在过去的几年中&#xff0c;容器技术得到了迅速的发展&#xff0c;并且在现代软件交付和部署中扮演着越来越重…

LVS-DR实验

实验前准备 DR服务器&#xff1a;192.168.188.11 192.168.188.15 NFS服务器&#xff1a;192.168.188.14 Web服务器1&#xff1a;192.168.188.12 Web服务器2&#xff1a;192.168.188.13 Vip&#xff1a;192.168.188.188 客户端&#xff1a;192.168.188.200 配置负载均衡调度…

leetCode 77.组合 + 回溯算法 (bactracking) + 剪枝 + 图解 + 笔记

77. 组合 - 力扣&#xff08;LeetCode&#xff09; 给定两个整数 n 和 k&#xff0c;返回范围 [1, n] 中所有可能的 k 个数的组合。可以按 任何顺序 返回答案。 示例 1&#xff1a; 输入&#xff1a;n 4, k 2 输出&#xff1a; [[2,4],[3,4],[2,3],[1,2],[1,3],[1,4], ] …

Unity EventSystem的一些理解和使用

Unity的EventSystem是用于处理用户输入和交互的系统。它是Unity UI系统的核心组件之一&#xff0c;可以用于捕捉和分发各种事件&#xff0c;例如点击、拖拽、按键、射线等。 常用的属性和方法有以下这些&#xff1a; 属性&#xff1a; current: 获取当前的EventSystem实例。…

Vue简易的车牌输入键盘,可以根据需要修改

效果图如下&#xff1a; 代码如下&#xff1a; <template><div><div class"carNoBoxInput"><div style"padding: 6px;border: 2px solid #fff;border-radius: 6px;margin: 6px 3px 6px 6px;"><input class"inputBox"…

小航助学题库蓝桥杯题库stem选拔赛(23年8月)(含题库教师学生账号)

需要在线模拟训练的题库账号请点击 小航助学编程在线模拟试卷系统&#xff08;含题库答题软件账号&#xff09;_程序猿下山的博客-CSDN博客 需要在线模拟训练的题库账号请点击 小航助学编程在线模拟试卷系统&#xff08;含题库答题软件账号&#xff09;_程序猿下山的博客-CSD…

利用ambari搭建Hbase高可用

初始环境&#xff1a; 节点名称服务名ambari-hadoop1ambari-hadoop2region serverambari-hadoop3hmater、 region server 计划为ambari-hadoop1添加hmaster&#xff0c;以避免hmaster的单点故障、 step1&#xff1a;添加备用Hmaster step2&#xff1a;选择ambari-hadoop1作为…

【一周AI简讯】OpenAI奥特曼王者归来,马斯克AI模型Grok下周开放测试,ChatGPT语音对话功能向所有用户免费开放

OpenAI奥特曼王者归来&#xff0c;董事会改组 终于&#xff0c;经历大约5天的极限拉扯&#xff0c;年底AI界吃瓜大戏落下帷幕&#xff0c;奥特曼确认回归。 ChatGPT语音对话功能向所有用户免费开放 ChatGPT 语音输入最初于 9 月份推出&#xff0c;标题是“ChatGPT 现在可以看…

尚硅谷大数据项目《在线教育之实时数仓》笔记008

视频地址&#xff1a;尚硅谷大数据项目《在线教育之实时数仓》_哔哩哔哩_bilibili 目录 第10章 数仓开发之DWS层 P066 P067 P068 P069 P070 P071 P072 P073 P074 P075 P076 P077 P078 P079 P080 P081 P082 第10章 数仓开发之DWS层 P066 第10章 数仓开发之DW…

消失的数字,旋转数组(leetcode 一题多解)

目录 一、消失的数字 思路一&#xff08;暴力求解&#xff09;代码实现&#xff1a; 思路二&#xff08;数列的思想&#xff09;代码实现&#xff1a; 思路三&#xff08;异或的运用&#xff09;代码实现&#xff1a; 二、轮转数组 思路一&#xff08;暴力求解&#xff09…

Vue3 + Scss 实现主题切换效果

Vue3 Scss 实现主题切换效果 先给大家看一下主题切换的效果&#xff1a; 像这样的效果实现起来并不难&#xff0c;只是比较麻烦&#xff0c;目前我知道的有两种方式可以实现&#xff0c;分别是 CSS 变量、样式文件切换&#xff0c;下面是该效果的核心实现方法 CSS变量 给…

电脑如何定时关机?

电脑如何定时关机&#xff1f;我承认自己是个相当粗心的人&#xff0c;尤其是在急于离开时经常会忘记关闭电脑&#xff0c;结果就是电量耗尽&#xff0c;导致电脑自动关机。而且&#xff0c;在我使用电脑的时候&#xff0c;经常需要进行软件下载、更新等任务。如果我一直坐等任…

设计模式—迪米特原则(LOD)

1.背景 1987年秋天由美国Northeastern University的Ian Holland提出&#xff0c;被UML的创始者之一Booch等普及。后来&#xff0c;因为在经典著作《 The Pragmatic Programmer》而广为人知。 2.概念 迪米特法则&#xff08;Law of Demeter&#xff09;又叫作最少知识原则&…

c语言,输入整数n(行数,本例为4),按照如下规则打印数字图片 1 5 9 13 2 6 10 14 3 7 11 15 4 8 12 16

c语言&#xff0c;输入整数n(行数&#xff0c;本例为4&#xff09;&#xff0c;按照如下规则打印数字图片 1 5 9 13 2 6 10 14 3 7 11 15 4 8 12 16 以下是使用C语言编写的程序&#xff0c;根据输入的行数打印数字图片的规则&#xff1a; #include <stdio.h>int main() …

CV计算机视觉每日开源代码Paper with code速览-2023.11.22

点击CV计算机视觉&#xff0c;关注更多CV干货 论文已打包&#xff0c;点击进入—>下载界面 点击加入—>CV计算机视觉交流群 1.【语义分割】Mobile-Seed: Joint Semantic Segmentation and Boundary Detection for Mobile Robots 论文地址&#xff1a;https://arxiv.or…

7种SQL进阶用法【转】

1.自定义排序(ORDER BY FIELD) 在MySQL中ORDER BY排序除了可以用ASC和DESC之外,还可以使使用自定义排序方式来实现 CREATE TABLE movies ( id INT PRIMARY KEY AUTO_INCREMENT, movie_name VARCHAR(255), actors VARCHAR(255), price DECIMAL(10,2) DEFAULT 50, release date…

ES 8.x开始(docker-compose安装、kibana使用、java操作)

学习文档地址 一、Docker安装 这里使用docker-compose来安装&#xff0c;方便后续迁移&#xff0c;Elasticserach和kibina一起安装。 1、创建安装目录 configdataplugins 2、配置文件 配置文件有两个&#xff0c;一个是ES的配置文件&#xff0c;一个docker-compose的配置文件 …

龙芯loongarch64服务器编译安装pyarrow

1、简介 pyarrow是一个高效的Python库,用于在Python应用程序和Apache Arrow之间进行交互。Arrow是一种跨语言的内存格式,可以快速高效地转移大型数据集合。它提供了一种通用的数据格式,将数据在内存中表示为表格,并支持诸如序列化和分布式读取等功能。 龙芯的Python仓库安…