Java数据结构之线段树的原理与实现
作者:Carol 发布时间:2021-12-17 13:30:52
标签:Java,数据结构,线段树
简介
线段树是一种二叉搜索树,是用来维护区间信息的数据结构。可以在O(logN)的时间复杂度内实现单点修改、区间修改、区间查询(区间求和,求区间最大值,求区间最小值)等操作。接下来我以实现区间求和为例子来讲解线段树(最大值和最小值与求和实现方式几乎无异),假设存在一个数组[1,4,6,3,9]。
实现思路
从线段树的定义,我们首先需要定义一个树节点,节点包含区间和(23),区间([1-5]),左节点,右节点等。(如果要实现求区间最大值,最小值,则还需包含这些)。然后需要提供构建线段树,线段树支持修改节点操作方法。
节点定义
@Data
public static class Node {
//区间起始下标
private int start;
//区间结尾下标
private int end;
//当前区间和值
private int value;
private Node left;
private Node right;
Node(int start, int end, int value) {
this.start = start;
this.end = end;
this.value = value;
}
}
构建线段树
因为构建线段树时候需要计算当前区间和,所以我们可以先初始化一个前缀和数组,在构建线段树时候利用下标快速计算出区间和,同时为了保证每个节点有一致的操作,初始化一个头节点,指向root(这是链表树等常用的简化操作的方法)
//head 指向线段树root节点的指针,使得root节点与其余节点操作保持一致
Node head;
int size;
List<Integer> nums;
//前缀和数组,便于构建线段树时候计算区间值,用于初次构建辅助
List<Integer> prefixSum ;
public void init(List<Integer> nums) {
//初始化一个头节点,便于操作
this.head = new Node(-1, -1, -1);
this.nums = nums;
//初始化前缀和数组
prefixSum = new ArrayList<>(nums.size());
prefixSum.add(0);
for (int i = 0; i < nums.size() ; i++) {
prefixSum.add(prefixSum.get(prefixSum.size() - 1) + nums.get(i));
}
//构建线段树
this.build(1, nums.size());
size = nums.size();
}
//构建线段树
public void build(int start, int end) {
Node root = new Node(start, end, prefixSum.get(end) - prefixSum.get(start - 1));
//将头节点右子树指向root
head.right = root;
//从root开始构建线段树
this.madeChild(root, start, end);
}
private void madeChild(Node node, int start, int end) {
if (start >= end) {
return;
}
//分个左右子树,左子树取start~mid,右子树取mid+1~end
int mid = start + ((end - start) >> 1);
if (start <= mid) {
Node left = new Node(start, mid, prefixSum.get(mid) - prefixSum.get(start - 1));
node.left = left;
madeChild(left, start, mid);
}
if (mid + 1 <= end) {
Node right = new Node(mid + 1, end, prefixSum.get(end) - prefixSum.get(mid));
node.right = right;
madeChild(right, mid + 1, end);
}
}
求解区间和
求解区间和过程就是遍历线段树,将求解区间与当前节点区间进行比较,如果全部存在于左子树或者右子树,则直接深度继续在左子树右子树遍历即可,但是如果求解区间在当前节点的左右子树均有部分,则需要将当前区间分为两个部分,然后分别深度遍历左右子树,最后将结果相加。
//求解区间和
public int findSectionSum(int start, int end) {
//深度遍历线段树,找到对应区间
if (start < 1 || end > size || start > end) {
return -1;
}
return dfsFindSectionSum(head.right, start, end);
}
/**
* 深度遍历线段树结构,分为三种情况
* 1.区间在当前节点的左子树中
* 2.区间在当前节点的右子树中
* 3.左子树中一部分,右子树中一部分
* @param node
* @param start
* @param end
* @return
*/
private int dfsFindSectionSum(Node node, int start, int end) {
if (node.start == start && node.end == end) {
//找到区间
return node.value;
}
if (this.isContain(node.left.start, node.left.end, start, end)) {
//在左子树中
return this.dfsFindSectionSum(node.left, start, end);
}
if (this.isContain(node.right.start, node.right.end, start, end)) {
//包含在右子树中
return this.dfsFindSectionSum(node.right, start, end);
}
//左边一部分,右边一部分
return this.dfsFindSectionSum(node.left, start, node.left.end) + this.dfsFindSectionSum(node.right, node.right.start, end);
}
/**
* 判断区间[start2, end2]是否包含在[start1, end1]中
* @param start1
* @param end1
* @param start2
* @param end2
* @return
*/
private boolean isContain(int start1, int end1, int start2, int end2){
return start2 >= start1 && end2 <= end1;
}
更新线段树
当更新指定位置元素的值的时候,我们需要将线段树中区间包含该节点的区间和进行更新。我们可以从根节点开始深度遍历线段树,如果当前节点包含该位置,我们更新区间和,然后根据当前节点左右子节点的区间,判断走左子树还是右子树,直至更新到叶子节点,则更新完成。
//更新线段树,将index位置的值更新为value,需要更新沿路的值
public void update(int index, int value) {
Node root = head.right;
while (null != root) {
if (index >= root.start && index <= root.end) {
root.value += value - nums.get(index - 1);
}
int mid = root.start + ((root.end - root.start) >> 1);
if (index <= mid) {
root = root.left;
continue;
}
root = root.right;
}
nums.set(index - 1, value);
}
来源:https://mp.weixin.qq.com/s/MV5Td4j3k8ROEV7yMhx5uQ


猜你喜欢
- 最近客户更新系统发现,以前的项目在调用相机的时候,闪退掉了,很奇怪,后来查阅后发现,Android 6.0以后需要程序授权相机权限,默认会给
- Kotlin定义变量一般有如下写法lateinit var name: String var age: String? = null那么用l
- 文件下载是一个软件开发中的常见需求。本文从最简单的下载方式开始步步递进,讲述了文件下载过程中的常见问题并给出了解决方案。并展示了如何使用多线
- Web UI项目中, 很多 Spring controller 视图函数直接返回 html 页面, 还有一些视图函数是要重定向或转发到其他的
- 本文实例为大家分享了C#实现飞行棋的具体代码,供大家参考,具体内容如下基于Winform框架写的不足之处请大佬指教using System;
- 本文实例为大家分享了java实现简单的图书管理系统的具体代码,供大家参考,具体内容如下一、项目分布Book类: 定义了书的一些属性(书名,作
- 如下所示:using System;using System.Collections.Generic;using System.Linq;u
- 由于项目需求,需要为Java提供一套支持事件驱动机制的类库,可以实现类似于C#中的event和delegate机制。众所周知,Java语言本
- 目录1 CompletionService介绍2 CompletionService源码分析3 CompletionService实现任务4
- 主要实现的功能:1.程序附带多张拼图随机拼图。2.可手动添加拼图。3.游戏成功判断。4.30秒超时判断。 Puzzle.csus
- 为什么我们要爬取数据在大数据时代,我们要获取更多数据,就要进行数据的挖掘、分析、筛选,比如当我们做一个项目的时候,需要大量真实的数据的时候,
- Java * 代理设计模式定义:为其他对象提供一种代理以控制对这个对象的访问。 * 使用java * 机制以巧妙的方式实现了代理模式的
- 本文实例为大家分享了Java身份证号码校验工具类的具体代码,供大家参考,具体内容如下import java.text.ParseExcept
- 本文实例为大家分享了java实现顺时针打印矩阵的具体代码,供大家参考,具体内容如下题目:输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每
- 递归生成一个如图的菜单,编写两个类数据模型Menu、和创建树形的MenuTree。通过以下过程实现:1.首先从菜单数据中获取所有根节点。2.
- list.remove最近做项目的过程中,需要用到list.remove()方法,结果发现两个有趣的坑,经过分析后找到原因,记录一下跟大家分
- 在做android开发时有这样一个需求,我们需要把地图的zoomcontroller放置于地图的右下角。 默认情况下,我们在eclipse中
- 前言Java的StringUtil.isEmpty(str)和"".equals(str)都是用来判断字符串是否为空的方
- 谷歌在推出Android5.0的同时推出了一些新控件,Android5.0中最常用的新控件有下面5种。1. CardView(卡片视图)Ca
- Activity是最基本的模块,一般称之为"活动",在应用程序中,一个Activity通常就是一个单独的屏幕。简单理解,