还未设置个性签名
成为VIP会员 享9项特权: 开通会员

完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>

3天内不再提示

一个数据结构-线段树

算法与数据结构 来源:算法与数据结构 2020-05-06 11:02 次阅读

一、概念解析

这次来介绍一个数据结构 - 线段树。

在平时刷题或是工作中,经常会遇到这么一个问题,“给定一个数组,求出数组某段区间的一些性质”。

比如给定一个数组 [5,2,6,1,-4,0,9,2],让你求出区间 [1,4] 上所有元素的和,在这个例子中,答案是 2 + 6 + 1 + (-4) = 5。

你可能会说,直接遍历一遍不就好了吗?

最简单的方式就是直接遍历一遍区间,时间复杂度也显而易见 O(n),如果在这个数组上频繁进行这个操作,那么效率相对来说会比较低,怎么优化呢?

对于求区间和的问题,前缀和数组是一个不错的选择,构建好前缀和数组后,求一个区间和的话只要前后一减就可以了,如果不算构建数组的时间,那么每次的操作时间复杂度就是 O(1)。

这里的问题在于前缀和数组只能解决求区间和的问题,但是其他的区间问题,前缀和数组并不能很好的解决,比如求某段区间上的最大值。

因此我们需要一个数据结构能够帮助我们解决大部分数组的区间问题,而且时间复杂度要尽可能的低。

这也就是今天的主题 - 线段树,首先要说明一点的是,线段树也是二叉树,只是它的节点里面含有区间的信息。

线段树每个节点表示的是一个区间,每个节点将其表示的区间一分为二,左边分到左子树,右边分到右子树,根节点表示的是整个区间(也就是整个数组),叶子节点表示的是一个 index(也就是单个元素),因为每次对半分的缘故,线段树构建出来是平衡的,也就是说树的高度是 O(logn),这里的 n 表示的是数组中所有的元素,这一点对于我们后面分析复杂度很重要。

线段树有三个基本的操作,分别是构建线段树(build)、区间查找(query)、还有就是修改(modify),假设我们现在需要解决的问题是 “求区间上的最大值”,例子还是之前的例子,一起来看看怎么实现这些操作。

对于构建操作来说,相对简单,你只需要记住 “自上而下而下递归分裂,自下而上回溯更新” 从根节点到叶子节点我们不断地将区间一分为二,从叶子节点开始返回值,一直到根节点,不断地更新区间信息。

查找操作是线段树的核心操作,考虑的情况相对较多,这里有四种情况:

情况一:节点区间包含查找区间。这种情况直接递归向下查找即可

情况二:节点区间不相交于查找区间。因为没有要查找的范围,停止搜索

情况三:节点区间相交但不包含查找区间。将区间分成两段,分别查找

情况四:节点区间相等于查找区间。直接返回答案

说明一下,这里说的 “包含” 的意思是一个区间全部元素都被另外一个区间涵盖,“相交” 的意思是一个区间的部分元素被另外一个区间涵盖,例如要查找的区间是 [1,3],那么 [0,4] 包含要查找的区间,[2,5] 只是相交要查找的区间。对于区间查找,后面有图解,跟着例子走一遍印象会更深刻。

最后一个修改操作,和构建操作类似,但有些许不同,你只需要记住 “自上而下递归查找,自下而上回溯更新”。修改的意思是,修改数组中的一个元素的值,这会影响相关的区间,相关的树节点,因此,相关联的节点也就需要更新。

线段树灵活的地方在于,树节点中存放的数据不同,它的功能就不同,比如说,你想要求解区间和,那么树节点中就存放对应区间元素的和,你想求解区间上的最大值,那么树节点中存放的就是对应区间上的最大值。不过话说回来,线段树并不是一个被广泛应用的数据结构,原因可能在于线段树的构建和使用相对于前缀和数组这样的技巧来说,稍微复杂了些。但是在解决数组区间的问题上,线段树可以提供一个还不错的思考方向。

二、动画描述

三、代码实现

publicclassSolution{ privateclassSegmentTreeNode{ intstart,end,max; SegmentTreeNodeleft,right; SegmentTreeNode(intstart,intend,intmax){ this.start=start; this.end=end; this.max=max; this.left=this.right=null; } } publicSegmentTreeNodebuild(intstart,intend,int[]nums){ if(start>end){ returnnull; } if(start==end){ returnnewSegmentTreeNode(start,end,nums[start]); } SegmentTreeNodenode=newSegmentTreeNode(start,end,nums[start]); //自上而下而下递归分裂 if(start!=end){ intmid=(start+end)/2; node.left=build(start,mid,nums); node.right=build(mid+1,end,nums); } //自下而上回溯更新 if(node.left!=null&&node.left.max>node.max){ node.max=node.left.max; } if(node.right!=null&&node.right.max>node.max){ node.max=node.right.max; } returnnode.max; } publicintquery(SegmentTreeNoderoot,intstart,intend){ //如果节点区间相等于查找区间,直接返回对应的值即可 if(root.start==start&&root.end==end){ returnroot.max; } intmid=(root.start+root.end)/2; intleftMax=Integer.MIN_VALUE,rightMax=Integer.MIN_VALUE; //判断是否需要去左子树查找 if(start<= mid) {             // 节点相交查找区间的情况             if (end >mid){ leftMax=query(root.left,start,mid); }//节点包含查找区间的情况 else{ leftMax=query(root.left,start,end); } } //判断是否需要去右子树查找 if(mid< end) {             // 节点相交查找区间的情况             if (start <= mid) {                 rightMax = query(root.right, mid + 1, end);             } // 节点包含查找区间的情况             else {                 rightMax = query(root.right, start, end);             }         }         return Math.max(leftMax, rightMax);     }     public void modify(SegmentTreeNode root, int index, int value) {         // 找到对应的叶子节点,进行元素更新         if (index == root.start && index == root.end) {             root.max = value;         }         if (root.start >=root.end){ return; } //自上而下递归查找 modify(root.left,index,value); modify(root.right,index,value); //自下而上回溯更新 root.max=Math.max(root.left.max,root.right.max); } }

四、复杂度分析

三个操作中,构建树的操作的时间复杂度是 O(n),原因也很好解释,构建的树有 2n 个节点,你可能会问这个 2n 是怎么得到的,思考这个问题可以从叶子节点出发,一共有 n 个叶子节点,构建操作是从上往下不断二分,这样保证了树的平衡,因此所有节点个数就是 n + n/2 + n/4 + ... + 1 = 2n。

由于构建每个节点只花了 O(1) 的时间,因此整个构建的时间复杂度就是 O(2n),忽略常数项,也就是 O(n)。

修改和查找都是沿着一条或者几条从上到下的路径进行的,因为树的高度是 O(logn),所以这两个操作的时间复杂度也是 O(logn)。可以看到这个时间复杂度比暴力的 O(n) 还是快不少。

  • 数据结构
    +关注

    关注

    3

    文章

    347

    浏览量

    38393
  • 二叉树
    +关注

    关注

    0

    文章

    51

    浏览量

    11307
  • 数组
    +关注

    关注

    1

    文章

    194

    浏览量

    23559
收藏 人收藏

    评论

    相关推荐

    指针进阶第五站:函数指针!

    定义一个函数指针,指向的函数有两个int形参并且返回一个函数指针,返回的指针指向一个有一个int形参....
    发表于 08-17 15:58 29次 阅读

    数组/指针的传参问题

    自定义函数里形参的类型,要和函数调用中传过去的实参类型相对应
    的头像 C语言编程学习基地 发表于 08-17 10:37 119次 阅读

    如何求三个数组的共同元素

    设置一个当前值和当前值的计数器,初始化当前值为数组首元素,计数器值为1,然后从第二个元素开始遍历整个....
    的头像 一口Linux 发表于 08-17 09:27 45次 阅读

    关于数组常见的面试题

    数组是最基本的数据结构,关于数组的面试题也屡见不鲜,本文罗列了一些常见的面试题,仅供参考。目前有以下....
    的头像 一口Linux 发表于 08-17 09:25 39次 阅读

    【C语言进阶】常见数据格式转换处理的代码实现

    本文简要介绍了几个常用数据的格式转换,希望对大家有所帮助。
    的头像 嵌入式物联网开发 发表于 08-17 08:48 122次 阅读

    C语言-学生管理系统(结构体+数组实现)

    这篇文章就使用结构体知识点完成一个小练习,使用结构体+数组设计一个简单的学生管理系统,作为结构体知识....
    的头像 DS小龙哥-嵌入式技术 发表于 08-14 10:07 163次 阅读

    C语言-字符串处理

    字符串在C语言里使用非常多,因为很多数据处理都是文本,也就是字符串,特别是设备交互、web网页交互返....
    的头像 DS小龙哥-嵌入式技术 发表于 08-14 10:05 162次 阅读

    C语言-数组

    C语言的数组是一个同类型数据的集合,主要用来存储一堆同类型的数据。
    的头像 DS小龙哥-嵌入式技术 发表于 08-14 09:59 160次 阅读

    C语言_数组的查找、替换、排序、拼接

    这篇文章主要是总结C语言的位运算几个实战例子,接着介绍数组的基本定义用法、数组排序、插入、拼接、删除....
    的头像 DS小龙哥-嵌入式技术 发表于 08-14 09:48 140次 阅读

    C语言总结_数组知识

    当前文章复盘C语言的数组: 数组定义规则、数组空间类型、数组下标使用、数组存放的数据类型、数组数据替....
    的头像 DS小龙哥-嵌入式技术 发表于 08-14 09:38 61次 阅读

    C语言总结_数组全方位练习

    C语言数组的练习题:涉及到数组插入、数组删除、数组下标数据的左移右移、数组排序、数组排序优化、数组的....
    的头像 DS小龙哥-嵌入式技术 发表于 08-14 09:34 53次 阅读

    指针数组的示例说明

    数组是一种类型的数的集合
    的头像 C语言编程学习基地 发表于 08-12 16:27 162次 阅读

    关于数组的求和

    数组是最基本的数据结构,关于数组的面试题也屡见不鲜,本文罗列了一些常见的面试题,仅供参考,如果您有更....
    的头像 city_prolove 发表于 08-10 11:25 123次 阅读

    描述u-boot驱动模型的数据结构

    u-boot有一个功能强大的驱动模型,这一点与linux内核一致。驱动模型对设备驱动相关操作做了一个....
    的头像 city_prolove 发表于 08-08 14:52 78次 阅读

    时间调度问题的千层套路

    会议室可以看做一个背包,每个会议可以看做一个物品,物品的价值就是会议的时长,请问你如何选择物品(会议....
    的头像 算法与数据结构 发表于 08-08 14:14 71次 阅读

    一种全新的数据蒸馏方法来加速NeRF

    学术界已有不少研究工作来加速 NeRF。比较流行的一种方式是, 给定训练好的 NeRF, 采用更高效....
    的头像 city_prolove 发表于 08-08 10:53 71次 阅读

    二叉树的统一迭代法

    我们以中序遍历为例,在二叉树:听说递归能做的,栈也能做!中提到说使用栈的话,无法同时解决访问节点(遍....
    发表于 08-03 11:22 59次 阅读

    LeetCode 560:和为K的子数组

    利用前缀和这种特点,可以快速的计算某个区间内的和,比如前 i 个元素的前缀和为 preSum[i] ....
    的头像 算法与数据结构 发表于 08-02 14:17 60次 阅读

    一文详解epoll的实现原理

    本文以四个方面介绍epoll的实现原理,1.epoll的数据结构;2.协议栈如何与epoll通信;3....
    的头像 一口Linux 发表于 08-01 13:28 159次 阅读

    二叉树的最大深度

    精简之后的代码根本看不出是哪种遍历方式,也看不出递归三部曲的步骤,所以如果对二叉树的操作还不熟练,尽....
    的头像 算法与数据结构 发表于 07-26 11:28 106次 阅读

    用迭代法编写二叉树的前后中序遍历案例

    递归的实现就是:每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中,然后递归返回的时....
    发表于 07-25 15:40 62次 阅读

    bootmem分配器使用的数据结构

    在内核初始化的过程中需要分配内存,内核提供了临时的引导内存分配器,在页分配器和块分配器初始化完毕后,....
    的头像 Linux阅码场 发表于 07-22 11:18 261次 阅读

    为什么可以用迭代法来实现二叉树的前后中序遍历呢

    我们在栈与队列:匹配问题都是栈的强项中提到了,递归的实现就是:每一次递归调用都会把函数的局部变量、参....
    的头像 算法与数据结构 发表于 07-19 11:50 350次 阅读

    C语言指针和数组的区别

    在C语言教程中我们使用通过数组名通过偏移和指针偏移都可以遍历数组,那么指针和数组到底有什么区别??
    的头像 C语言编程学习基地 发表于 07-18 16:29 337次 阅读

    怎么就能构造成二叉树呢?

    一直跟着公众号学算法的录友 应该知道,我在二叉树:构造二叉树登场!,已经讲过,只有 中序与后序 和 ....
    的头像 算法与数据结构 发表于 07-14 11:20 245次 阅读

    如何求递归算法的时间复杂度

    相信很多同学对递归算法的时间复杂度都很模糊,那么这篇Carl来给大家通透的讲一讲。
    的头像 算法与数据结构 发表于 07-13 11:33 263次 阅读

    OpenHarmony内核任务间IPC原理

    身为深开鸿 OS 内核开发师,我们常年深耕于 OpenHarmony 的内核开发,希望通过分享一些工....
    的头像 深开鸿 发表于 07-12 16:45 386次 阅读

    ARRAY类型变量的赋值

    用数组名后跟在方括号内的适当的索引值来寻地数组的单个元素。每维一个索引,他们用逗号分开并处于同一方括....
    的头像 机器人及PLC自动化应用 发表于 07-12 10:55 237次 阅读

    论动态规划穷举的两种视角

    P(n, k)(也有很多书写成A(n, k))表示从n个不同元素中拿出k个元素的排列(Permuta....
    的头像 算法与数据结构 发表于 07-11 14:49 214次 阅读

    【C语言进阶】4种方法将bin文件转换为C语言的数组

    【C语言进阶】4种方法将bin文件转换为C语言的数组
    的头像 嵌入式物联网开发 发表于 07-11 09:19 1558次 阅读
    【C语言进阶】4种方法将bin文件转换为C语言的数组

    CH376中的RealCount具体怎么设置呢?

    函数UINT8CH376ByteWrite( PUINT8 buf, UINT16 ReqCount, PUINT16 RealCount )中的RealCount具体怎...
    发表于 07-11 07:09 149次 阅读

    Uint16的数组需要存储,怎么通过CH376ByteWrite这个函数来存?

    我有一个Uint16的数组需要存储 怎么通过CH376ByteWrite 这个函数来存? 谢谢    ...
    发表于 07-11 06:05 172次 阅读

    DECL:对变量、数组和常数进行声明

    如果 CHAR 类型数组的所有数组元素都拥有相同的字符串,则不必单独初始化每个数组元素。忽略右侧的数....
    的头像 机器人及PLC自动化应用 发表于 07-10 10:27 118次 阅读

    嵌入式系统中数据结构的重要性

    通常我们可以通过UART,I2C,SPI等接口将数据读取到单片机内部。很多情况下,数据的更新频率很高....
    发表于 07-08 16:33 133次 阅读

    基于二叉树的多层的液晶菜单界面设计

    以前用单片机做用户交互的菜单的时候,都比较痛苦,如何写一个复用性高,方便维护,可扩展性高的GUI框架....
    的头像 硬件攻城狮 发表于 07-08 14:27 302次 阅读

    高频系列:单词拆分问题

    现在给你一个不包含重复单词的单词列表wordDict和一个字符串s,请你判断是否可以从wordDic....
    的头像 算法与数据结构 发表于 07-07 09:25 123次 阅读

    KUKA机器人高级编程SWRITE的用法

    在数组的情况下,规范“Z”可用于定义要考虑的数组元素的数量。如果没有为“Z”指定值,则考虑所有数组元....
    的头像 机器人及PLC自动化应用 发表于 07-05 14:32 176次 阅读

    BOM的数据结构及内涵

    在产品研制和生产过程中,不同的部门结合各自任务将各个阶段的产品信息划分为不同的BOM信息。BOM的数....
    的头像 工业互联网前线 发表于 07-05 14:16 243次 阅读

    一个适用于单片机裸机开发的开源轮子

    今天推荐一个适用于单片机裸机开发的开源轮子。
    发表于 07-04 18:38 1045次 阅读

    1365.有多少小于当前数字的数字

    用一个哈希表hash(本题可以就用一个数组)来做数值和下标的映射。这样就可以通过数值快速知道下标(也....
    的头像 算法与数据结构 发表于 06-30 09:39 98次 阅读

    微擎表单验证

    ./oschina_soft/gitee-w7-engine-validate.zip
    发表于 06-29 14:44 17次 阅读
    微擎表单验证

    Java反射机制清空字符串导致业务异常分析

    JVM为了提高性能和减少内存开销,在实例化字符串常量时进行了优化。JVM在Java堆上开辟了一个字符....
    的头像 openEuler 发表于 06-22 11:17 197次 阅读

    树状数组可以很简单

    那能不能找到一种间断式的前缀和呢,只需要统计前面区间中的部分元素。这样在修改某个a[i]的时候就不会....
    的头像 算法与数据结构 发表于 06-21 09:27 185次 阅读

    Assimp 3D模型导入库

    ./oschina_soft/assimp.zip
    发表于 06-16 09:53 45次 阅读
    Assimp 3D模型导入库

    从数据结构和算法解析OpenHarmony的事件机制

    近年来,国内开源实现跨越式发展,并成为企业提升创新能力、生产力、协作和透明度的关键。作为 OpenA....
    的头像 OpenAtom OpenHarmony 发表于 06-16 09:46 362次 阅读

    如何将灰度图转为3D点云

    X,Y是作为图像的行列坐标,Z是实数(表示的是深度/高度),而不是灰度,因为灰度值是0——255之间....
    的头像 新机器视觉 发表于 06-13 11:11 629次 阅读

    C/C++基础知识汇总

    这是一篇五万字的C/C++知识点总结,包括答案。
    的头像 Linux爱好者 发表于 06-12 15:10 542次 阅读

    贪心算法:分发饼干

    对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,....
    的头像 算法与数据结构 发表于 06-06 09:25 331次 阅读

    51单片机结构体数组1206显示

    用51单片机编写结构体数组,用1206显示
    发表于 06-06 09:17 59次 阅读

    数组一维中相同元素个数统计

    数组一维中相同元素个数统计
    发表于 05-31 16:48 37次 阅读

    请问ch549f如何把数组中的数据保存到dataflash中?

    请问,ch549f  如何把数组中的数据保存到dataflash中?我的代码不行,代码是修改官方代码而来。请大神看看哪里有问题,...
    发表于 05-27 06:53 370次 阅读

    CH554的USB接收缓冲区溢出怎么做处理?

    我在定义一个数组去接收USB端发送大于32字节的数据时,接收到的数据只有32字节后的数据,前面32字节的数据被覆盖了,发送32字...
    发表于 05-26 06:38 256次 阅读

    结构体数组的初始化

    先定义一个结构体 typedef struct __TEST__ {     unsigned char a;         ...
    发表于 05-21 15:24 4790次 阅读

    数组元素超过9个如何根据 Array 自动创建 Cluster

    数组元素超过9个如何根据 Array 自动创建 Cluster ...
    发表于 04-10 14:28 7474次 阅读
    数组元素超过9个如何根据 Array 自动创建 Cluster

    如何利用Arm-2D函数去实现一种显示汉字的设计呢

    玩过Arm-2D的同学都知道,官方的lcd_printf函数只提供了一种6 * 8点阵的字库,而且是不支持显示汉字的。想想也是,Arm-2D是给...
    发表于 04-02 15:56 999次 阅读

    openHarmony IPC数据调用的过程分享

    1.前情概览 我们在前片博客中讲述了 proxy - stub 架构的一般编程范式,这篇文章关注驱动自身的数据传输,做一次完整的数据分析...
    发表于 03-30 09:26 1820次 阅读

    用rt_memset或者memset给数组清零只能清零一半的数组是为什么?

    用rt_memset或者memset给数组清零的时候,只能清零一半的数组,用的单片机是ch32v103r8t6 手里只有这一个开发板。没办法测试其他...
    发表于 03-25 14:41 1244次 阅读

    为什么C语言通过数组下标越界访问却不报错

    为什么C语言通过数组下标越界访问却不报错?这是什么原因?...
    发表于 02-25 07:34 666次 阅读