当前位置: 首页 > news >正文

基础数据结构--线段树(Python版本)

文章目录

  • 前言
  • 特点
  • 操作
    • 数据存储
    • update
    • Lazy下移
    • 查询
  • 实现

前言

月末了,划个水,赶一下指标(更新一些活跃值,狗头)
本文主要是关于线段树的内容。这个线段树的话,主要是适合求解我们一个数组的一些区间的问题,例如区间之和,区间乘机,区间最大,最小值等(当然求和,求乘机啥的,直接用前缀数组,如果是一些区间的大小的问题的话,当然用这个是比较合适的,当然这依然是空间换取时间的操作。例如一个数组长度为N,那么当我们构建这颗线段树时,我们所需要花费的空间为4N(为了保证不越界).

特点

首先的话,要说的关于线段树的特点其实就几个,第一就是数据存放在叶子节点,非叶子节点表示的是我们想要求取的目标值,例如我们想要求取一个区间和,那么非叶子节点存储的就是这个小区间内的值。

第二个特点就是Lazy懒惰更新,这个有点类似于摊还分析当中提到的第二种方式,每个元素的花费需要考虑到当前的消费和将来的消费,将来的消费,用于将来的花费。这个Lazy其实也有类似的意思,我先标记一下,然后我要用的到的时候,我再进行操作,起到了一个预知未来,延迟操作的意思。同样的,代码实现比较简单,至少比红黑树,斐波那契堆简单。

那么关于这个特点的话,这里先插一个眼,具体的将在下面进行阐述。
本文的话,就从区间求和为案例进行说明,这里面可以覆盖到较多的操作。

操作

数据存储

首先的话,这个数据的存储其实就是下面的样子。

在这里插入图片描述
然后这个叶子节点的话就是我们的这个数据,然后的话,这里也是对半砍掉一组数据,然后递归,跟那个归并有点像。

update

然后就是修改,这个的话就开始体现到Lazy的作用了,首先我们知道一个节点,他其实表示了当前这个节点表示的是哪个区间的一个值,用代码表示他的一个数据结构其实就是这样的:

class __Node():l: int = 0r: int = 0v: int = 0lazy: int = 0def __str__(self):return "left:{},right{},value:{},lazy:{}".format(self.l, self.r, self.v, self.lazy)

所以这个Update的话,明确一个区间,然后呢,我们找到这个区间,然后秉承着lazy的原则,如果我们发现,如果我们要更新的区间能够覆盖我们当前的这个节点的区间,我们就直接更新好这个节点的值,然后这个Lazy,记录一些我们修改的值是啥。

    def update(self, i, l, r, k):if (self.tree[i].l >= l and self.tree[i].r <= r):self.tree[i].v += k * (self.tree[i].r - self.tree[i].l + 1)self.tree[i].lazy = kreturnif (self.tree[i].lazy != 0):self.__putdown(i)if (self.tree[2 * i].r >= l): #和左孩子还有交集self.update(2 * i, l, r, k)if (self.tree[2 * i + 1].l <= r): #和右孩子还有交集self.update(2 * i + 1, l, r, k)self.tree[i].v = self.tree[2*i].v+self.tree[2*i+1].v

之后的话,我们跟新一下,当然这里还需要注意的是,就是如果没有完全覆盖的话,我们需要更新一下Lazy,此时给到孩子节点,为什么要更新呢,原因的话就是当前的节点已经不能覆盖了,需要用到孩子节点,但是原来孩子节点没有更新值,现在要用了,就得把孩子赶紧更新一下,然后重新更新当前作为父节点的i。

Lazy下移

这个下移的话就是刚刚提到的,因为这个Lazy就相当于一个标记。他是这样的。

    def __putdown(self, i):self.tree[2 * i].lazy += self.tree[i].lazyself.tree[2 * i + 1].lazy += self.tree[i].lazymid = (self.tree[i].l + self.tree[i].r) // 2self.tree[2 * i].v += self.tree[i].lazy * (mid - self.tree[i].l + 1)self.tree[2 * i + 1].v += self.tree[i].lazy * (self.tree[i].r - mid)self.tree[i].lazy = 0

更新孩子的Lazy,然后去掉父节点的Lazy,然后更新值。

查询

查询也是一致的,和更新一样,只是少了元素的更新,这里依然需要这个Lazy的下移,而且其实这个Lazy的下移其实就是在重新计算我们的修改,假设一直都没有用到,就一直不会更新,这样就节省了运算。就比如,你买了一张4080ti,但是你一直没有时间happy,那么在你没有happy时间的情况下就提前买了显卡,那么就浪费了这个money,因为早买没有享受到,但是当你有happy time的时候,你再去买,那么就是及时享乐了,没有造成资源的空闲浪费,搞不好还降价了,嘿嘿~

def search(self, i, l, r):if (self.tree[i].l >= l and self.tree[i].r <= r):return self.tree[i].vif (self.tree[i].lazy != 0):self.__putdown(i)t = 0if (self.tree[2 * i].r >= l):t += self.search(2 * i, l, r)if (self.tree[2 * i + 1].l <= r):t += self.search(2 * i + 1, l, r)return t

实现


""" 
为了方便建树,这里的话我们将从1开始作为我们的下标
"""
class SegmentTree(object):def __init__(self, date):self.date = [0] + dateself.len_date = len(self.date)self.tree = [self.__Node() for _ in range(4 * self.len_date)]self.__build(1, 1, self.len_date - 1)def __build(self, i, l, r):self.tree[i].l = lself.tree[i].r = rif (l == r):self.tree[i].v = self.date[r]returnmid = (l + r) // 2self.__build(2*i, l, mid)self.__build(2*i+1, mid + 1, r)self.tree[i].v = self.tree[i * 2].v + self.tree[i * 2 + 1].vdef search(self, i, l, r):if (self.tree[i].l >= l and self.tree[i].r <= r):return self.tree[i].vif (self.tree[i].lazy != 0):self.__putdown(i)t = 0if (self.tree[2 * i].r >= l):t += self.search(2 * i, l, r)if (self.tree[2 * i + 1].l <= r):t += self.search(2 * i + 1, l, r)return tdef update(self, i, l, r, k):if (self.tree[i].l >= l and self.tree[i].r <= r):self.tree[i].v += k * (self.tree[i].r - self.tree[i].l + 1)self.tree[i].lazy = kreturnif (self.tree[i].lazy != 0):self.__putdown(i)if (self.tree[2 * i].r >= l):self.update(2 * i, l, r, k)if (self.tree[2 * i + 1].l <= r):self.update(2 * i + 1, l, r, k)self.tree[i].v = self.tree[2*i].v+self.tree[2*i+1].vdef __putdown(self, i):self.tree[2 * i].lazy = self.tree[i].lazyself.tree[2 * i + 1].lazy = self.tree[i].lazymid = (self.tree[i].l + self.tree[i].r) // 2self.tree[2 * i].v += self.tree[i].lazy * (mid - self.tree[i].l + 1)self.tree[2 * i + 1].v += self.tree[i].lazy * (self.tree[i].r - mid)self.tree[i].lazy = 0class __Node():l: int = 0r: int = 0v: int = 0lazy: int = 0def __str__(self):return "left:{},right{},value:{},lazy:{}".format(self.l, self.r, self.v, self.lazy)

这里的话,注意,找的时候呢,是从1号节点开始的,1号节点不等于第一个元素!

if __name__ == '__main__':a = [1,2,3,4,5]seg = SegmentTree(a)seg.update(1,5,5,5) #从根节点开始找,更新区间为[5,5]的元素+5,也就是第五个元素+5print(seg.search(1, 4, 5))#从根节点开始找,查找区间为[4,5]的区间和

🆗,over!

http://www.lryc.cn/news/21723.html

相关文章:

  • 【micropython】SPI触摸屏开发
  • 【云原生】k8s中Pod进阶资源限制与探针
  • AI - stable-diffusion(AI绘画)的搭建与使用
  • 应用场景五: 西门子PLC通过Modbus协议连接DCS系统
  • 我继续问了ChatGPT关于SAP顾问职业发展前景的问题,大家感受一下
  • Python小白入门---00开篇介绍(简单了解一下)
  • 【算法基础】C++STL容器
  • 【经典蓝牙】蓝牙 A2DP协议分析
  • Objective-C 构造方法的定义和声明规范
  • Matlab图像处理学习笔记
  • 笔记(三)——迭代器的基础理论知识
  • 没有公网ip怎么外网访问nas?快解析内网端口映射到公网
  • spring integration使用:消息转换器
  • Vue3电商项目实战-商品详情模块7【21-商品详情-评价组件-头部渲染、22-商品详情-评价组件-实现列表】
  • 地址,指针,指针变量是什么?他们的区别?符号(*)在不同位置的解释?
  • 【MongoDB】一、MongoDB的安装与部署
  • 《爆肝整理》保姆级系列教程python接口自动化(二十三)--unittest断言——上(详解)
  • MySQL的mvcc
  • vite:常见的配置
  • 计算机图形学:liang算法和Cyrus-Beck算法
  • React组件之间的通信方式总结(上)
  • C++17 nodiscard标记符
  • SAP 寄售业务的标准流程
  • 操作系统高频知识
  • 加载预训练模型,模型微调,在自己的数据集上快速出效果
  • VScode远程连接服务器-过程试图写入的管道不存在-could not establist connection to【已解决】
  • 电子技术——B类输出阶
  • 【老卫搬砖】034期:HarmonyOS 3.1 Beta 1初体验,我在本地模拟器里面刷短视频
  • Day901.内部临时表 -MySQL实战
  • jstatd的启动方式与关闭方式