线段树
时间:2022-07-26
本文章向大家介绍线段树,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
对于给定数组nums,修改nums中某个下标的值的操作记做update(index, val),获得nums某个区间元素和的操作记做query(start,end)。
若直接对原数组nums操作,update操作时间复杂度为O(1),query操作为O(N),引入前缀和数组之后,query的时间复杂度是变为O(1),但是update的时间复杂度又变为O(N)。
为了降低上述两操作的平均时间复杂度,引入线段树这种数据结构,使得update 和 query的时间复杂度都变为O(log(N))。
线段树的每个节点存储某一个段区间之和,其中每个结点的左子树和右子树分别存储当前结点的前半段之和和后半段之和,叶子结点存储的线段长度为1,根结点存储整个数组之和。
如下举例说明:
对于nums = [1, 2, 3, 4, 5, 6],线段树结构如下图所示:
由于我们发现其构成的线段树类似完全二叉树。因此可以使用像大/小根堆中的存储二叉树的方式存储该树。left = parent * 2 + 1,right = parent * 2 + 2。
建树过程
一般使用4倍的原数组的大小存储该树。
对于当前结点,首先完成左孩子和右孩子的创建,之后其的值等于左右孩子值之和。
baseline为当前结点为叶子结点时,当前结点值即为nums元素值。
public class IntervalTree {
int[] nums;
int[] tree;
public IntervalTree(int[] nums) {
this.nums = nums;
this.tree = new int[nums.length * 4];
create(0, 0, nums.length - 1);
}
// left 和 right为nodeIndex对应的nums的线段区间
public void create(int nodeIndex, int left, int right) {
if(left == right) {
tree[nodeIndex] = nums[left];
return;
}
int mid = (left + right) / 2;
create(nodeIndex * 2 + 1, left, mid);
create(nodeIndex * 2 + 2, mid + 1, right);
tree[nodeIndex] = tree[nodeIndex * 2 + 1] + tree[nodeIndex * 2 + 2];
}
}
单点修改
直观过程是修改当前位置对应数中的叶子结点,然后依次一层一层网上遍历,修改其父亲节点对应的值。
还是使用递归求解,代码与建树过程类似,不过需要注意的是不需要走完全树,只需走完对应的部分即可。
public void update(int index, int val) {
update(0, 0, nums.length - 1, index, val);
}
public void update(int nodeIndex, int left, int right, int index, int val) {
if(left == right) {
tree[nodeIndex] = nums[left] = val;
return;
}
int mid = (left + right) / 2;
if(index <= mid) {
update(nodeIndex * 2 + 1, left, mid, index, val);
}else {
update(nodeIndex * 2 + 2, mid + 1, right, index, val);
}
tree[nodeIndex] = tree[nodeIndex * 2 + 1] + tree[nodeIndex * 2 + 2];
}
区间查询
代码类似建树过程,不过只需计算与当前区间有交集的部分。
public int query(int start, int end) {
return query(0, 0, nums.length - 1, start, end);
}
public int query(int nodeIndex, int left, int right, int start, int end) {
if(start > right || end < left) {
return 0;
}
if(left >= start && right <= end) {
return tree[nodeIndex];
}
int mid = (left + right) / 2;
return query(nodeIndex * 2 + 1, left, mid, start, end)
+ query(nodeIndex * 2 + 2, mid + 1, right, start, end);
}
完整代码如下:
public class IntervalTree {
int[] nums;
int[] tree;
public IntervalTree(int[] nums) {
this.nums = nums;
this.tree = new int[nums.length * 4];
create(0, 0, nums.length - 1);
}
public void create(int nodeIndex, int left, int right) {
if(left == right) {
tree[nodeIndex] = nums[left];
return;
}
int mid = (left + right) / 2;
create(nodeIndex * 2 + 1, left, mid);
create(nodeIndex * 2 + 2, mid + 1, right);
tree[nodeIndex] = tree[nodeIndex * 2 + 1] + tree[nodeIndex * 2 + 2];
}
public void update(int index, int val) {
update(0, 0, nums.length - 1, index, val);
}
public void update(int nodeIndex, int left, int right, int index, int val) {
if(left == right) {
tree[nodeIndex] = nums[left] = val;
return;
}
int mid = (left + right) / 2;
if(index <= mid) {
update(nodeIndex * 2 + 1, left, mid, index, val);
}else {
update(nodeIndex * 2 + 2, mid + 1, right, index, val);
}
tree[nodeIndex] = tree[nodeIndex * 2 + 1] + tree[nodeIndex * 2 + 2];
}
public int query(int start, int end) {
return query(0, 0, nums.length - 1, start, end);
}
public int query(int nodeIndex, int left, int right, int start, int end) {
if(start > right || end < left) {
return 0;
}
if(left >= start && right <= end) {
return tree[nodeIndex];
}
int mid = (left + right) / 2;
return query(nodeIndex * 2 + 1, left, mid, start, end)
+ query(nodeIndex * 2 + 2, mid + 1, right, start, end);
}
}
- JAVA中使用Jedis操作Redis
- Tomcat搭建文件服务器
- Windows下SLmail邮件服务器缓冲区溢出理解及实验
- java使用mina和websocket通信
- 【机器学习】10 种机器学习算法的要点
- 写一个BASIC认证的https协议
- java发送邮件功能,以发送qq邮件为例
- spring boot加载复杂的yml文件获取不到值的问题
- JUC包下的CountDownLatch,CyclicBarrier,Semaphore
- java队列,ArrayBlockingQueue
- 多线程使用wait和notify做生产者消费者模型导致线程全部假死
- 偏执的iOS逆向研究员:收集全版本的macOS iOS+越狱+内核调试
- java 多线程暂停与恢复:suspend,resume
- ArrayList底层实现
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 当一个http请求来临时,SpringMVC究竟偷偷帮你做了什么?
- Js实现文本复制
- 当一个http请求来临时,SpringMVC究竟偷偷帮你做了什么?处理器映射器与处理器篇
- anetTcpGenericConnect 详解
- 详解 MySQL 基准测试和sysbench工具
- 第六天:网络处理(anet部分)-- redis源码慢慢学,慢慢看【redis6.0.6】
- python爬王者荣耀壁纸
- 搞定三大神器之 Python 装饰器
- 当一个http请求来临时,SpringMVC究竟偷偷帮你做了什么?请求映射器篇
- rabbitpy使用purge不生效
- Springboot读取自定义属性之集合(list,数组)
- 被遗忘的 10 个Linux命令,很实用!
- Nginx配置中一个不起眼字符"/"的巨大作用,失之毫厘谬以千里
- 当一个http请求来临时,SpringMVC究竟偷偷帮你做了什么?SpringMVC视图处理器与视图篇章【终章】
- 求求你,别再开发的时候一用redis分布式锁,就急着去复制粘贴了!lua脚本的实现思路