简介

线段树是一种二叉搜索树,是用来维护区间信息的数据结构。可以在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 线段树的资料请关注Devmax其它相关文章!

Java数据结构之线段树的原理与实现的更多相关文章

  1. swift篇第一期:简单的数据结构

    首先我们可以去使用Playground来编码,并且会实时的显示对应的编码信息,这样我们就不用每次都去运行程序来显示输出的东西了哦,也方便了我们对某些语句的验证,这个是比较赞的var与let前者为可变修饰符,后者为不可变从字面意思我们就可以很好的区分了常用的类型呢,跟其他语言基本相同啦,主要有几种:1.int类型2.Float,Double类型3.String类型4.Boolean类型当我们去声明一

  2. Swift 集合数据结构性能分析

    本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请发送邮件至dio@foxmail.com举报,一经查实,本站将立刻删除。

  3. Swift中的集合类数据结构

    在那种情况下,你将会需要一种基本的集合类数据结构。继续学习,你将会比较成熟的Cocoa数据结构与对应的纯Swift结构的性能。常见iOS数据结构iOS中三种最常用的数据结构是arrays,dictionaries和sets。除了在Swift和Objective-C中旧的Foundation框架中的数据结构,现在又有了新的仅支持Swift版本的数据结构与语言紧密结合在一起。Swift数组是同质的,意味着每一个Swift数组都只包含一种类型的对象。

  4. 11.Swift 中的类和结构体

    举例来说,以下情境中适合使用结构体:1.几何形状的大小,封装一个width属性和height属性,两者均为Double类型。这次就讲到这里,下次我们继续

  5. a place you can learn algorithms and data structures(算法和数据结构) in swift

    https://github.com/raywenderlich/swift-algorithm-club

  6. Swift3.0 类和结构体的选择

    结构体实例总是通过值传递,类实例总是通过引用传递先说说值类型和引用类型的区别值类型被赋予给一个变量、常量或者被传递给一个函数的时候,其值会被拷贝在Swift中,所有的结构体和枚举类型都是值类型。实际中,这意味着绝大部分的自定义数据构造都应该是类,而非结构体”Swift中,许多基本类型,诸如String,Array和Dictionary类型均以结构体的形式实现。Objective-C中Nsstring,NSArray和NSDictionary类型均以类的形式实现,而并非结构体。

  7. 【Swift】结构体和类

    Swift中结构体和类有很多共同点与结构体相比,类还有如下的附加功能:结构体和枚举是值类型值类型被赋予给一个变量、常量或者被传递给一个函数的时候,其值会被拷贝。为了达到这个目的,Swift内建了两个恒等运算符:类和结构体的选择在你的代码中,你可以使用类和结构体来定义你的自定义数据类型。实际中,这意味着绝大部分的自定义数据构造都应该是类,而非结构体。Swift中,许多基本类型,诸如String,Array和Dictionary类型均以结构体的形式实现。

  8. 如何在Swift中创建打包数据结构?

    我正在将一个项目从Objective-C转换为Swift,我正在使用一个打包的结构来输入通过套接字发送的转换二进制消息:我不确定Swift中最好的方法是什么,我能得到的最接近的近似值是:翻译中丢失了两个重要的细节:没有保证整数类型的比特,并且没有结构打包.我不认为这可以在Swift中表达,但如果是这样,怎么样?

  9. android – 如何正确删除保留的实例片段

    解决方法正如@Luksprog所建议的,以下方法有效.但是,它仍然无法解释为什么通过onDetach完成的先前清理不起作用.如果有人能解释为什么这个解决方案有效,而以前没有,我会非常感激.版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请发送邮件至dio@foxmail.com举报,一经查实,本站将立刻删除。

  10. android – 数组适配器notifyDataSetChanged()将无法正常工作

    .有任何想法吗?

随机推荐

  1. 基于EJB技术的商务预订系统的开发

    用EJB结构开发的应用程序是可伸缩的、事务型的、多用户安全的。总的来说,EJB是一个组件事务监控的标准服务器端的组件模型。基于EJB技术的系统结构模型EJB结构是一个服务端组件结构,是一个层次性结构,其结构模型如图1所示。图2:商务预订系统的构架EntityBean是为了现实世界的对象建造的模型,这些对象通常是数据库的一些持久记录。

  2. Java利用POI实现导入导出Excel表格

    这篇文章主要为大家详细介绍了Java利用POI实现导入导出Excel表格,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  3. Mybatis分页插件PageHelper手写实现示例

    这篇文章主要为大家介绍了Mybatis分页插件PageHelper手写实现示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  4. (jsp/html)网页上嵌入播放器(常用播放器代码整理)

    网页上嵌入播放器,只要在HTML上添加以上代码就OK了,下面整理了一些常用的播放器代码,总有一款适合你,感兴趣的朋友可以参考下哈,希望对你有所帮助

  5. Java 阻塞队列BlockingQueue详解

    本文详细介绍了BlockingQueue家庭中的所有成员,包括他们各自的功能以及常见使用场景,通过实例代码介绍了Java 阻塞队列BlockingQueue的相关知识,需要的朋友可以参考下

  6. Java异常Exception详细讲解

    异常就是不正常,比如当我们身体出现了异常我们会根据身体情况选择喝开水、吃药、看病、等 异常处理方法。 java异常处理机制是我们java语言使用异常处理机制为程序提供了错误处理的能力,程序出现的错误,程序可以安全的退出,以保证程序正常的运行等

  7. Java Bean 作用域及它的几种类型介绍

    这篇文章主要介绍了Java Bean作用域及它的几种类型介绍,Spring框架作为一个管理Bean的IoC容器,那么Bean自然是Spring中的重要资源了,那Bean的作用域又是什么,接下来我们一起进入文章详细学习吧

  8. 面试突击之跨域问题的解决方案详解

    跨域问题本质是浏览器的一种保护机制,它的初衷是为了保证用户的安全,防止恶意网站窃取数据。那怎么解决这个问题呢?接下来我们一起来看

  9. Mybatis-Plus接口BaseMapper与Services使用详解

    这篇文章主要为大家介绍了Mybatis-Plus接口BaseMapper与Services使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  10. mybatis-plus雪花算法增强idworker的实现

    今天聊聊在mybatis-plus中引入分布式ID生成框架idworker,进一步增强实现生成分布式唯一ID,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

返回
顶部