Java数据结构之线段树的原理与实现

 更新时间:2022年06月15日 09:19:50   作者:Carol  
线段树是一种二叉搜索树,是用来维护区间信息的数据结构。本文将利用示例详细讲讲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);
  }

以上就是Java数据结构之线段树的原理与实现的详细内容,更多关于Java 线段树的资料请关注脚本之家其它相关文章!

相关文章

  • Spring3.1.1+MyBatis3.1.1的增、删、查、改以及分页和事务管理

    Spring3.1.1+MyBatis3.1.1的增、删、查、改以及分页和事务管理

    这篇文章主要介绍了Spring3.1.1+MyBatis3.1.1的增、删、查、改以及分页和事务管理的相关资料,需要的朋友可以参考下
    2016-01-01
  • SpringBoot Knife4j框架&Knife4j的显示内容的配置方式

    SpringBoot Knife4j框架&Knife4j的显示内容的配置方式

    Knife4j框架是基于Swagger2开发的在线API文档生成工具,主要功能包括自动生成API文档、接口文档展示、接口测试工具、接口权限控制和在线调试,该框架支持通过注解自动生成详细的接口文档,开发者可以直接在文档界面进行接口测试和调试
    2024-09-09
  • 简单了解java局部变量与成员变量的区别

    简单了解java局部变量与成员变量的区别

    这篇文章主要介绍了简单了解java局部变量与成员变量的区别,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-10-10
  • Springboot项目的Mapper中增加一个新的sql语句

    Springboot项目的Mapper中增加一个新的sql语句

    本文主要介绍了Springboot项目的Mapper中增加一个新的sql语句,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-05-05
  • java环境变量配置和adb的配置教程详解

    java环境变量配置和adb的配置教程详解

    这篇文章主要介绍了java环境变量配置和adb的配置教程,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-05-05
  • java反射机制示例

    java反射机制示例

    这篇文章主要介绍了java反射机制示例,需要的朋友可以参考下
    2014-04-04
  • java swing GUI窗口美化方式

    java swing GUI窗口美化方式

    这篇文章主要介绍了java swing GUI窗口美化方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-10-10
  • Spring BeanUtils忽略空值拷贝的方法示例代码

    Spring BeanUtils忽略空值拷贝的方法示例代码

    本文用示例介绍Spring(SpringBoot)如何使用BeanUtils拷贝对象属性忽略空置,忽略null值拷贝属性的用法,代码简单易懂,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
    2022-03-03
  • java中Pulsar InterruptedException 异常

    java中Pulsar InterruptedException 异常

    这篇文章主要为大家介绍了java中Pulsar InterruptedException 异常分析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-02-02
  • spring @Transactional注解中常用参数详解

    spring @Transactional注解中常用参数详解

    这篇文章主要介绍了spring @Transactional注解中常用参数详解,事物注解方式: @Transactional,本文结合实例代码给大家介绍的非常详细,需要的朋友可以参考下
    2024-02-02

最新评论