线段树:解决区间查询和区间修改的利器

线段树是一种非常有用的数据结构,它可以在 O ( log ⁡ n ) O(\log n) O(logn) 的时间内支持区间修改和查询。在本文中,我们将介绍线段树的基本概念和实现方法,并通过一个例子来说明其使用方法。

基本概念

线段树是一种二叉树结构,用于处理区间查询和修改操作。它将一个区间分成多个小的子区间,然后用树状结构来表示这些子区间。每个节点代表一个区间,它的左右儿子代表该区间的两个子区间。叶子节点表示区间中的单个元素。

线段树的建立是通过递归的方式进行的。首先将整个区间分成两半,然后分别递归地建立左右子树。每个节点保存了其对应区间内的信息,例如区间和、最大值等。

实现方法

下面是一个简单的线段树实现。我们使用 JavaScript 来演示。假设我们要实现一个支持区间求和操作的线段树,下面是代码实现:

class SegmentTree {
  constructor(nums) {
    this.nums = nums;
    this.tree = new Array(nums.length * 4).fill(0);
    this.build(0, 0, nums.length - 1);
  }

  build(node, start, end) {
    if (start === end) {
      this.tree[node] = this.nums[start];
    } else {
      const mid = Math.floor((start + end) / 2);
      this.build(node * 2 + 1, start, mid);
      this.build(node * 2 + 2, mid + 1, end);
      this.tree[node] = this.tree[node * 2 + 1] + this.tree[node * 2 + 2];
    }
  }

  update(node, start, end, i, val) {
    if (start === end) {
      this.nums[i] = val;
      this.tree[node] = val;
    } else {
      const mid = Math.floor((start + end) / 2);
      if (i >= start && i <= mid) {
        this.update(node * 2 + 1, start, mid, i, val);
      } else {
        this.update(node * 2 + 2, mid + 1, end, i, val);
      }
      this.tree[node] = this.tree[node * 2 + 1] + this.tree[node * 2 + 2];
    }
  }

  query(node, start, end, i, j) {
    if (i > end || j < start) {
      return 0;
    }
    if (i <= start && j >= end) {
      return this.tree[node];
    }
    const mid = Math.floor((start + end) / 2);
    return this.query(node * 2 + 1, start, mid, i, j) + this.query(node * 2 + 2, mid + 1, end, i, j);
  }
}

const nums = [1, 3, 5, 7, 9, 11];
const st = new SegmentTree(nums);

console.log(st)

应用实例

线段树的应用非常广泛,下面我们通过一个例子来说明其使用方法。假设我们有一个长度为 n n n 的数组 a a a,需要支持以下两种操作:

  1. 修改数组中的某个元素 a i a_i ai 的值为 v v v
  2. 查询区间 [ l , r ] [l, r] [l,r] 的元素和。

使用线段树可以很方便地实现这两个操作。我们首先需要建立一棵线段树,将数组中的元素保存在叶子节点上。每个节点保存了其对应区间内的元素和,以支持区间查询。

当需要修改某个元素时,我们可以使用线段树的 update 操作来实现。具体来说,我们从根节点开始递归,找到对应叶子节点,并将其值修改为新的值 v v v。同时,我们还需要更新该节点对应的父节点、祖先节点的值,以保证线段树的正确性。

当需要查询某个区间的元素和时,我们可以使用线段树的 query 操作来实现。具体来说,我们从根节点开始递归,找到包含区间 [ l , r ] [l, r] [l,r] 的节点。如果该节点的区间恰好为 [ l , r ] [l, r] [l,r],则直接返回其对应的元素和。否则,我们需要将区间 [ l , r ] [l, r] [l,r] 拆分成两个子区间 [ l , m i d ] [l, mid] [l,mid] [ m i d + 1 , r ] [mid+1, r] [mid+1,r],分别递归查询左右子树,最后将其结果合并即可。

下面是基于线段树实现的一个例子:

class SegmentTree {
  constructor(nums) {
    this.nums = nums;
    this.tree = new Array(nums.length * 4).fill(0);
    this.build(0, 0, nums.length - 1);
  }

  build(node, start, end) {
    if (start === end) {
      this.tree[node] = this.nums[start];
    } else {
      const mid = Math.floor((start + end) / 2);
      this.build(node * 2 + 1, start, mid);
      this.build(node * 2 + 2, mid + 1, end);
      this.tree[node] = this.tree[node * 2 + 1] + this.tree[node * 2 + 2];
    }
  }

  update(node, start, end, i, val) {
    if (start === end) {
      this.nums[i] = val;
      this.tree[node] = val;
    } else {
      const mid = Math.floor((start + end) / 2);
      if (i >= start && i <= mid) {
        this.update(node * 2 + 1, start, mid, i, val);
      } else {
        this.update(node * 2 + 2, mid + 1, end, i, val);
      }
      this.tree[node] = this.tree[node * 2 + 1] + this.tree[node * 2 + 2];
    }
  }

  query(node, start, end, i, j) {
    if (i > end || j < start) {
      return 0;
	} else if (i <= start && j >= end) {
		return this.tree[node];
	} else {
		const mid = Math.floor((start + end) / 2);
		const leftSum = this.query(node * 2 + 1, start, mid, i, j);
		const rightSum = this.query(node * 2 + 2, mid + 1, end, i, j);
		return leftSum + rightSum;
	}
  }
}

// 使用示例
const nums = [1, 3, 5, 7, 9, 11];
const tree = new SegmentTree(nums);

console.log(tree.query(0, 0, nums.length - 1, 2, 4)); // 输出 15
tree.update(0, 0, nums.length - 1, 3, 8);
console.log(tree.query(0, 0, nums.length - 1, 2, 4)); // 输出 18

在上面的代码中,我们定义了一个名为 SegmentTree 的类,它包含了建立线段树、修改元素、查询区间元素和等方法。在构造函数中,我们先初始化了线段树的数组,并通过 build 方法建立线段树。在 update 方法中,我们首先递归找到要修改的节点,然后将其值修改为新的值,同时更新该节点的父节点和祖先节点。在 query 方法中,我们首先判断当前节点的区间与查询区间是否有交集,如果没有则返回 0;如果当前节点的区间包含在查询区间中,则直接返回该节点的元素和;否则我们需要将查询区间拆分成两个子区间,分别递归查询左右子树,最后将其结果合并。

通过上面的代码,我们可以方便地实现对数组的修改和区间求和操作,而且时间复杂度为 O ( log ⁡ n ) O(\log n) O(logn)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Activity登堂入室

1.Activity,Window与View的关系 下面是自己查阅资料,看了下一点源码的归纳所得,如果哪写错了欢迎指出!下面贴下小结图: 流程解析:Activity调用startActivity后最后会调用attach方法,然后在PolicyManager实现一个Ipolicy接口,接着实现一个Policy对象,接着调用makeneww…

STM32学习(十三)

通用定时器简介 通用定时器有TIM2、TIM3、TIM4、TIM516位递增、递减、中心对齐计数器&#xff08;0~65535&#xff09;16位预分频器&#xff08;1~65536&#xff09;可用于触发DAC、ADC在更新事件、触发事件、输入捕获、输出比较时&#xff0c;会产生中断/DMA请求。4个独立通道…

讲解有哪些实用的数据恢复工具

我们在日常电脑使用中&#xff0c;很容易出现一些意想不到的情况&#xff0c;比如误删文件、格式化存储介质、遭受常见的电子黑客攻击或者因为其它原因造成的故障&#xff0c;都会造成重大的硬盘数据丢失。这时候我们可以考虑使用一些可靠的数据恢复工具免费版进行找回。下面我…

【C语言】整形数据的存储和读取过程

文章目录一. 问题引入二. 存储过程三. 读取过程一. 问题引入 观察下面一段代码&#xff1a; #include <iostream> using namespace std;int main() {unsigned int a -10;printf("%d\n", a);// -10printf("%u\n", a);// 4294967286return 0; }最终…

Autodesk AutoCAD 2023(CAD设计软件)自动化工具介绍以及图文安装教程

Autodesk AutoCAD是一款功能强大的计算机辅助设计软件&#xff0c;主要用于2D和3D设计、制图和草图。它适用于多种行业&#xff0c;包括建筑、土木工程、机械工程、电气工程等等。 Autodesk AutoCAD具有2D和3D设计、多种工具和功能、可扩展性、与其他Autodesk软件集成和多平台…

Python项目部署上线

&#x1f4da;故事背景&#xff1a;最近在开发一个python的脚本&#x1f332;&#xff0c;闲着没事就研究了一下如何将python项目部署上线&#xff0c;&#x1f4dd;记录一下自己踩过的坑&#xff0c;希望本片文章可以帮助到你&#x1f44d;。&#x1f527;使用工具&#xff1a…

【Docker学习笔记】9.Docker Machine及Swarm 集群管理

前言 本章介绍Docker Machine及Swarm 集群管理。 Docker Machine 简介 Docker Machine 是一种可以让您在虚拟主机上安装 Docker 的工具&#xff0c;并可以使用 docker-machine 命令来管理主机。 Docker Machine 也可以集中管理所有的 docker 主机&#xff0c;比如快速的给 …

13.Template Method模板方法(行为型模式)

一、动机&#xff08;Motivation&#xff09; <1>在软件构建过程中&#xff0c;对于某一项任务&#xff0c;它常常有稳定的整体操作结构&#xff0c;但各个子步骤却有很多改变的需求&#xff0c;或者由于固有的原因&#xff08;比如框架与应用之间的关系&#xff09;而无…

ChatGPT编程秀:做一个简单爬虫程序

随着ChatGPT的大火&#xff0c;越来越多的人习惯于用ChatGPT搞一些有趣的事。对于一个资深的爬虫程序来说&#xff0c;体验下ChatGPT做爬虫程序也是很有意思的事情。 首先想想我们的问题域&#xff0c;我想到几个问题&#xff1a; 不能用HTTP请求去爬&#xff0c;如果我直接用…

JDBC数据库驱动的下载与安装与连接

目录 JDBC数据库驱动下载 Intellij IDEA安装JDBC驱动 在使用 JDBC 之前&#xff0c;需要下载相应的 JDBC 驱动程序&#xff0c;该驱动程序应该与你使用的数据库的版本相对应。可以在数据库官网上找到相应的 JDBC 驱动程序。 JDBC数据库驱动下载 点击官方链接 MySQL :: MySQ…

LeetCode-119. 杨辉三角 II

目录题目思路动态规划题目来源 119. 杨辉三角 II 题目思路 先把这个List<List< String >>转换成二维数组&#xff0c;从题目可以看出都依赖于上面的两个数 动态规划 1.确定dp数组&#xff08;dp table&#xff09;以及下标的含义 根据上面的题目思路&#xf…

Azure SQL基础到实战(2)-部署

目录Azure 上的数据库服务的演变Azure SQL 部署选项Azure 虚拟机上的 SQL ServerIaaS 与PaaS无版本数据库服务SQL 托管实例SQL 数据库弹性数据库池Azure 上的数据库服务的演变 Azure SQL 是 Microsoft 作为 Azure 云计算平台的一部分提供的云数据库产品/服务。 与其他版本的 S…

提高工作效率,这 10 款 AI 工具不能错过

1. ChatGPT ChatGPT 是最近特别火爆的一款 AI 工具&#xff0c;它能够通过理解和学习人类的语言来进行对话&#xff0c;还能根据聊天的上下文进行互动&#xff0c;真正像人类一样来聊天交流&#xff0c;甚至能完成撰写邮件、视频脚本、文案、翻译、代码&#xff0c;写论文等任…

2020年 第11届 蓝桥杯 Java B组 省赛真题详解及小结【第1场省赛 2020.7.5】

目录 一、试题 A: 解密 二、试题 B: 纪念日 三、试题 C: 合并检测 四、试题 D: 分配口罩 五、试题 E: 斐波那契数列最大公约数 六、试题F: 分类计数 七、试题G: 八次求和 八、试题 H: 字符串编码 九、试题 I: BST 插入节点问题 十、试题 J: 网络分析 小结 一、试题 …

Linux第二次总结

Linux阶段总结 OSI模型&#xff1a;应用层、表示层、会话层、传输层、网络层、数据链路层、物理层 路由器的工作原理&#xff1a;最佳路径选择 三次握手四次挥手&#xff1a;... shell是翻译官把人类语言翻译成二进制语言 Tab作用&#xff1a;自动补齐、确认输入是否有误 …

51单片机(IIC协议OLED屏)

一、IIC协议 1、IIC协议概述 1.1、概述&#xff1a;IIC全称Inter-Integrated Circuit (集成电路总线) 是由PHILIPS公司在80年代开发的两线式串行总线&#xff0c;用于连接微控制器及其外围设备。IIC属于半双 工同步通信方式 1.2、特点&#xff1a;简单性和有效性。 由于接口直…

【JavaEE】多线程之阻塞队列(BlockingQueue)

目录 1.了解阻塞队列 2.生产者消费者模型又是什么&#xff1f; 2.1生产者消费者模型的优点 2.1.1降低服务器与服务器之间耦合度 2.1.2“削峰填谷”平衡消费者和生产的处理能力 3.标准库中的阻塞队列&#xff08;BlockingQueue&#xff09; 3.1基于标准库&#xff08;Bloc…

树莓派Linux源码配置,树莓派Linux内核编译,树莓派Linux内核更换

目录 一 树莓派Linux的源码配置 ① 内核源码下载说明 ② 三种方法配置源码 二 树莓派Linux内核编译 ① 内核编译 ② 编译时报错及解决方案&#xff08;亲测&#xff09; 三 更换树莓派Linux内核 操作步骤说明 ● dmesg报错及解决方案&#xff08;亲测&#xff0…

链路上小段线的阻抗突变到底会不会影响信号质量?

一博高速先生成员&#xff1a;刘春 在进行PCB设计时&#xff0c;相信有经验的工程师都遇到过这种情况&#xff0c;在布线过程中&#xff0c;有时候由于电路结构或空间限制&#xff0c;需要中途某段走线变粗或变细&#xff0c;如串接电阻电容、下孔、BGA出线区域或走线密集区域…

【vscode】调试cocos creator

先看看 官方教程–使用 VS Code 调试网页版游戏 一、安装插件 Debugger for Chrome已弃用 安装 JavaScript Debugger (Nightly) 插件替代&#xff0c;其他步骤完全一样。 二、通过cocos creator打开任意js脚本&#xff0c;按F5&#xff0c;选择Web 应用&#xff08;Chrome&…
最新文章