使用TreeMap数据结构解决独特的搜索需求

news2024/11/15 9:48:49

TreeMap是Java集合,它以有序的键及其相应的值的形式组织数据。它自JDK 1.2以来就已经存在。在内部,TreeMap使用红黑树来组织数据,这是一种自平衡二叉树。TreeMap中的键是唯一的标识符,默认情况下,TreeMap会根据键的自然顺序来排列数据。

对于整数键,它们是按键的升序存储的;对于String键,数据将按字母顺序存储。我们总是可以覆盖数据的默认排序,并在创建TreeMap时使用比较器来按照比较器中提供的逻辑以任何定义的方式进行自定义排序。在这篇文章中,我们将详细讨论一些自定义混合排序方法,并提供一些代码示例。

最流行的搜索和排序数据结构之一是二叉搜索树(BST)。当涉及到搜索操作性能时,二叉搜索树具有最坏情况下的复杂度为O(n),这几乎与线性搜索相同。

红黑树对于每次插入和删除操作都有一个恒定的时间复杂度O(log n),因为它在每次操作后自我平衡,确保树始终是平衡的。这意味着树会自动以最优方式进行调整,这确保了搜索和查找操作非常高效,并且对于任何数据集以及任何类型的搜索操作,时间复杂度都不会超过O(log n)。

它使用一个简单但强大的机制,通过为树中的每个节点着色为红色或黑色来维持一个平衡的结构。它可以被称为自平衡二叉搜索树。它具有良好、高效的最坏情况下的运行时间复杂度。

【squids.cn】 全网zui低价RDS,免费的迁移工具DBMotion、数据库备份工具DBTwin、SQL开发工具等


为什么使用TreeMap? 

大多数BST操作(如搜索、删除、查找和更新)需要O(log n)的时间,其中n是二叉搜索树中的节点数。对于所有键都沿着一个特定路径的偏斜的二叉树,这些操作的成本可能会变为O(n),即它要么是一个左偏斜的二叉树,所有元素只沿左路径,要么是一个右偏斜的二叉搜索树,所有元素都沿二叉搜索树的右路径。这些类型的数据情况会使二叉搜索树变得非常高效,所有类型的操作都会非常耗时。

除了搜索操作外,插入或删除操作在最坏的情况下也将需要O(n)的时间复杂度。这种情况会导致一个完全不平衡的二叉搜索树,树的高度几乎等于树中的节点数。

如果我们确保每次插入和删除后树的高度保持在O(log n),那么我们可以确保所有这些操作的上界为O(log n),即使在数据高度偏斜的最坏情况下。这是通过引入红黑树来实现的,无论数据是偏斜还是非偏斜,树总是平衡的。红黑树的高度始终是O(log n),其中n是树中的节点数。

默认情况下,红黑树具有以下属性,这些属性在TreeMap数据集中是固有的:

  1. 每个节点在TreeMap中都有指向父节点、右子节点和左子节点的引用。如果某个节点不存在,则在TreeMap中可能有这三个引用或减少的引用。 

  2. 左子元素键的值总是小于父元素键的值。 

  3. 右子元素键的值总是大于或等于TreeMap中父元素键的值。 

  4. 每个树节点要么是红色,要么是黑色。 

  5. 树的根节点总是黑色的。 

  6. 从根到任何叶节点的每条路径必须有相同数量的黑色节点。 

  7. 不能有两个红色节点相邻;即红色节点不能是另一个红色节点的父节点或子节点。 

如果您查看下面的表格,红黑树在任何数据情况下的时间复杂度始终是相同的,它不受数据偏斜的影响,始终表现稳定。这使得TreeMap成为处理高容量数据集的非常高效和广泛使用的数据结构,在这些数据集中,我们不确定底层数据的偏斜度,并且我们可以在TreeMap上执行一些在其他映射数据结构(如HashMap)中不可能的独特操作。

时间复杂度 

操作平均情况最坏情况
搜索O(log n)O(log n)
插入O(log n)O(log n)
删除O(log n)O(log n)

在极端偏斜数据的情况下进行搜索操作,这是在二叉搜索树中组织数据的最坏情况,红黑树对于最坏的情况表现更好。红黑树可以用于高效地索引数据库中的数据,支持需要快速搜索和检索数据的技术。

在这篇文章中,我不打算详细解释红黑算法,但我们将重点介绍TreeMap提供的一些小众功能,TreeMap是红黑树的一个突出实现。

为何使用TreeMap 

Java中的TreeMap类提供了丰富的API,支持多种搜索操作。它不仅支持相等搜索操作(被搜索的元素存在于映射中),还支持其他的特殊搜索操作,如查找映射中比给定元素稍大或稍小的元素。

此外,还有给定范围内的映射的子映射。只有当键按照某种顺序排列时,这种复杂的搜索操作才是可能的,而TreeMap是非常适合处理此类处理需求的数据结构。搜索操作通常需要O(log n)时间来返回结果。与HashMap不同,虽然HashMap只需要常数时间,但执行搜索操作需要对数时间。这让人不禁想知道为什么它首先会被引入。

答案是,即使对于不属于HashMap的最坏情况类别的场景,HashMap以常数时间复杂度提供最优搜索,数据也会以连续的方式保存在内存中。而对于TreeMap,没有这样的限制,它只使用存储项目所需的内存量,与使用连续内存区域的HashMap不同。如上所述的另一个关键原因是,TreeMap提供了执行特殊搜索操作的功能,这不仅仅是在映射中找到给定的键,还可以找到其他键,这些键可能只是稍大、稍小或最小的键,或者是给定键范围的数据集的一部分。

与只提供等值搜索操作的HashMap不同,即要搜索的值等于Map中的任何键,TreeMap提供了以TreeMap的形式结构化的各种搜索操作的能力。搜索的能力使TreeMap成为解决复杂搜索用例的非常关键的数据结构,在我的经验中,我广泛地使用它来解决许多复杂的业务需求。以下是TreeMap提供的一些最主要的搜索功能。我们首先将熟悉TreeMap提供的基本功能,之后将讨论一些特殊的搜索需求,在这些需求中,我们将结合上述TreeMap功能来达到不同种类的搜索功能的特殊需求:

  1. floorKey(K key): 此函数在TreeMap中搜索小于或等于给定键的最大键。如果函数找不到这样的键,它将返回null。

  2. ceilingKey(K key): 此函数在TreeMap中搜索大于或等于给定键的最小键。如果函数找不到这样的键,它将返回null。

  3. higherKey(K key): 此函数在TreeMap中搜索严格大于给定键的最小键。如果函数找不到这样的键,它将返回null。

  4. lowerKey(K key): 此函数在TreeMap中搜索严格小于给定键的最大键。如果函数找不到这样的键,它将返回null。

  5. subMap(K from Key, K toKey): 此函数搜索此映射的部分视图,其键的范围从fromKey(包括)到toKey(不包括)。

  6. tailMap(K fromKey): 此函数搜索此映射的部分视图,其键大于或等于fromKey。

  7. headMap(K toKey): 此函数搜索与小于给定键的最小键关联的键值映射,或者如果没有这样的键则返回null。 

现在我们已经了解了TreeMap提供的关键功能和不同类型的搜索功能,我们将通过代码示例更好地理解它,稍后我们将尝试定义一些新的特殊搜索和数据结构功能,使用上面列出的TreeMap功能的不同组合:

使用案例

我们将使用下面捕获的员工实体。此类具有几个关键的员工特性,用于说明用例。

在以下员工类中,除了员工标识符属性外,我们还捕获了一些将在用例中使用的额外的不同属性。这些属性如下:

  1. lastYearRating: 此属性保存了员工去年的评估评级。

  2. tenure: 此属性保存了员工与组织共度的年数的四舍五入值。

  3. currentSalary: 此属性存储了员工的当前年薪。

class Employee {
     String empName;
     String designation;
     Integer empId;
     String lastYearRating;
     Integer tenure;
     Integer currentSalary;
 
     public Employee(String empName, String designation, Integer empId, String lastYearRating, Integer tenure, Integer currentSalary) {
         this.empName = empName;
         this.designation = designation;
         this.empId = empId;
         this.lastYearRating = lastYearRating;
         this.tenure = tenure;
         this.currentSalary = currentSalary;
     }
 
     @Override
     public String toString() {
         return "Employee{" +
                 "empName='" + empName + '\'' +
                 ", designation='" + designation + '\'' +
                 ", empId=" + empId +
                 ", lastYearRating='" + lastYearRating + '\'' +
                 ", tenure=" + tenure +
                 ", currentSalary=" + currentSalary +
                 '}';
     }
 
     public Integer getCurrentSalary() {
         return currentSalary;
     }
 
     public void setCurrentSalary(Integer currentSalary) {
         this.currentSalary = currentSalary;
     }
 
    public String getEmpName() {
         return empName;
     }
 
     public void setEmpName(String empName) {
         this.empName = empName;
     }
 
     public String getDesignation() {
         return designation;
     }
 
     public void setDesignation(String designation) {
         this.designation = designation;
     }
 
     public Integer getEmpId() {
         return empId;
     }
 
     public void setEmpId(Integer empId) {
         this.empId = empId;
     }
 
     public String getLastYearRating() {
         return lastYearRating;
     }
 
     public void setLastYearRating(String lastYearRating) {
         this.lastYearRating = lastYearRating;
     }
 
     public Integer getTenure() {
         return tenure;
     }
 
     public void setTenure(Integer tenure) {
         this.tenure = tenure;
     }
 
 }

在下面的代码示例中,我们将使用TreeMap的功能来解决一些特殊场景:

我们将按照组织的工龄分桶来对所有员工进行分桶:我们将有六个工龄桶,并将每个员工记录放在他们所属的正确桶中。这将使用TreeMap提供的一些特殊功能,并将以非常简化的方式展示。接下来,我们将为每个员工关联正确的工龄桶。

import java.util.ArrayList;
 import java.util.TreeMap;
 
 public class TreeMapCeilingFloorExample {
     public static void main(String[] args) {
         TreeMap<Integer, Object> tenureTreeMap = new TreeMap<>();
         /* employee List, for illustration purpose just creating few dummy entries but in actual it can be read from source
          * and can run into very high numbers including all employees for the organization */
         ArrayList<Employee> empList = new ArrayList<Employee>();
         empList.add(new Employee("John", "Engineer", 24, "A1", 6,150000));
         empList.add(new Employee("David", "Manager", 35, "A2", 3,145000));
         empList.add(new Employee("Fred", "Architect", 41, "B1", 2, 155000));
         empList.add(new Employee("Brad", "Engineer", 21, "A3", 8,120000));
         empList.add(new Employee("Jason", "Engineer", 32, "A1", 4,170000));
         empList.add(new Employee("Adam", "Senior Engineer", 12, "A1", 14,85000));
         empList.add(new Employee("Christie", "Director", 10, "A1", 15,178000));
         empList.add(new Employee("Martha", "Accountant", 26, "A1", 6,85000));
         empList.add(new Employee("Diana", "Engineer", 28, "A1", 6,108000));
         empList.add(new Employee("Lisa", "Developer", 14, "A1", 12,82000));
          /*  Adding the default buckets for the employee tenures with 0-5, 6- 10, 11-15, 16-20, 21-25, 26-30
          * Adding only higher bound as key so can efficiently use ceiling function to categorize all employees into right bucket */
         tenureTreeMap.put(5, new ArrayList<Employee>());
         tenureTreeMap.put(10, new ArrayList<Employee>());
         tenureTreeMap.put(15, new ArrayList<Employee>());
         tenureTreeMap.put(20, new ArrayList<Employee>());
         tenureTreeMap.put(25, new ArrayList<Employee>());
         tenureTreeMap.put(30, new ArrayList<Employee>());
         /* Using ceilingKey function to check for every employee record and adding the employee to the right bucket.
          * This is a simple illustration leveraging ceilingKey function to easily bucket employees into the right bucket */
         empList.forEach(employeeObj -> {
             Integer key = tenureTreeMap.ceilingKey(employeeObj.getTenure());
             ArrayList matchingList = (ArrayList) tenureTreeMap.get(key);
             matchingList.add(employeeObj);
             tenureTreeMap.put(key, matchingList);
         });
         // Printing all Employees in the bucket starting with lowest to highest
         tenureTreeMap.forEach((key, value) -> {
             System.out.println("Key : " + key + " Value : ");
             ((ArrayList) value).forEach(empObj -> System.out.println());
         });
 
        
     }
 
 }

结果

以下是工龄TreeMap的描述,其中每个员工都被归入正确的工龄类别。关键是工龄桶的上界,下界是先前记录的上界+1。这是根据给定范围结构化数据的有效方式。这里我们有六个工龄桶,如下:

  • 0年到5年:对于这个范围,关键是5,这是这个桶的上界。三个员工记录落入这个桶。 

  • 6年到10年:对于这个范围,关键是10,这是这个桶的上界。四个员工记录落入这个桶。 

  • 11年到15年:对于这个范围,关键是15,这是这个桶的上界。三个员工记录落入这个桶。 

  • 16年到20年:对于这个范围,关键是20,这是这个桶的上界。没有员工记录落入这个桶。 

  • 21年到25年:对于这个范围,关键是25,这是这个桶的上界。没有员工记录落入这个桶。 

  • 26年到30年:对于这个范围,关键是30,这是这个桶的上界。没有员工记录落入这个桶。

记住,TreeMap使用键的自然顺序(或者提供的自定义比较器)来确定元素的顺序。确保导入TreeMap类,并处理方法返回null的情况,如果没有适合的键按照你的用例。

以下代码函数为每个员工记录关联工龄范围。结果也打印在代码下面,显示每个员工记录的工龄范围。TreeMap是用工龄范围的上界作为键创建的,所有给定工龄范围的员工都在形式为数组列表的给定键对应的值中。

下面列出的代码使用天花板和地板函数在TreeMap中为任何键查找下界和上界。在下面的情况下,我们试图找出每个员工记录的工龄范围,通过使用lowerKey函数获得下界。接下来使用ceilingEntry方法确定上界,形成范围字符串,并与每个员工记录相关联。这是一个简单的插图,展示了TreeMap功能的力量,通过利用内置功能轻松完成复杂的需求。

结果:在下面的输出中,每个员工都有一个与该记录相关联的任期存储桶。

希望这篇文章帮助你熟悉了TreeMap数据结构,并帮助你使用内置的TreeMap函数来解决复杂的数据分类和搜索需求。

作者:Alejandro Duarte

更多技术干货请关注公众号 “云原生数据库”

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

医院如何提升设备维护效率?智能工单管理系统有什么用?

目前&#xff0c;许多大型医院楼宇面积庞大&#xff0c;人员流动频繁&#xff0c;各科室医疗设备繁多&#xff1b;不管是基础硬件设施、还是暖通、电力系统&#xff0c;日常都处于高频使用、运转状态。维修、维护需求多、压力大&#xff0c;存在以下诸多的问题。 1、报修难点  …

使用 Microchip SAM9X60 OTP 存储板卡的MAC地址和序列号

1. 介绍 SAM9X60 处理器有部分OTP&#xff08;One Time Programming&#xff09; Aera 可用于存储user data&#xff0c;这样的话我们就可以将板卡 MAC Address和 SN 序列号写到固定的OTP User Area中。 为什么要使用 OTP 区域存储MAC地址和序列号呢&#xff1f;答案是为了省钱…

C++内存池(1)理论基础及简单实现

一、内存池原理 1、我们先用生活中的例子来解释什么是内存池&#xff1a; &#xff08;1&#xff09;每个月月底钱花完时&#xff0c;或者急需要用钱时&#xff0c;你就打电话给你父母要钱&#xff0c;然后父母把钱通过微信或支付宝转给你。这种方式&#xff0c;每次要用钱…

如何在谷某地球飞行模拟中导入简单飞机开发的飞机模型

如何在谷某地球飞行模拟中导入简单飞机开发的飞机模型 简飞的飞友们&#xff01;我并没有弃坑&#xff0c;只不过我不是你们想象的那样设计飞机。我之前写过一篇图文讲解如何在谷某地球里规划飞行航线&#xff1a; 手把手教你驾驶西锐SR-22小飞机在美国大峡谷中穿行https://b…

Docker 恶意挖矿镜像应急实例

01、概述 当网络流量监控发现某台运行多个docker容器的主机主动连接到一个疑似挖矿矿池的地址时&#xff0c;需要快速响应和排查&#xff0c;以阻止进一步的损害。 面对docker容器的场景下&#xff0c;如何快速分析和识别恶意挖矿容器?本文将分享一种应急响应思路&#xff0c;…

【基础篇】三、SpringBoot基础配置

文章目录 0、模块的复制1、配置文件格式2、yaml语法3、yaml数据读取方式4、关于封装自定义对象来读取yaml配置的思考4、多环境启动5、配置文件分类 0、模块的复制 平时要大量创建模块时&#xff0c;可以直接复制模块&#xff0c;打开project的目录&#xff1a; 复制粘贴&#…

LeetCode142.环形链表-II

这道题和上一道题几乎没有任何区别啊&#xff0c;为什么还是中等难度&#xff0c;我用上一道题的解法一分钟就写出来了&#xff0c;只不过返回的不是true和false而是节点&#xff0c;以下是我的代码&#xff1a; public class Solution {public ListNode detectCycle(ListNode…

接口自动化测试推荐用什么框架?

在推荐接口自动化测试框架时&#xff0c;需要考虑多个因素&#xff0c;包括项目需求、技术栈、团队经验和个人偏好。 以下是几个常用的接口自动化测试框架供你参考&#xff1a; Postman&#xff1a; Postman是一个功能强大且易于上手的接口测试工具&#xff0c;它提供了许多…

卡尔曼滤波应用在数据处理方面的应用

卡尔曼滤波应用到交通领域 滤波器介绍核心思想核心公式一维卡尔曼滤波器示例导入所需的库 滤波器介绍 卡尔曼滤波器是一种用于估计系统状态的数学方法&#xff0c;它以卡尔曼核心思想为基础&#xff0c;广泛应用于估计动态系统的状态和滤除测量中的噪声。以下是卡尔曼滤波器的核…

学Python的漫画漫步进阶 -- 第十步

学Python的漫画漫步进阶 -- 第十步 十、异常处理10.1 第一个异常——除零异常10.2 捕获异常10.2.1 try-except语句10.2.2 多个except代码块10.2.3 多重异常捕获10.2.4 try-except语句嵌套 10.3 使用finally代码块释放资源10.4 自定义异常类10.5 动动手——手动引发异常10.6 练一…

安卓可视大屏寻呼台 兼容标准sip协议

SV-A32i 安卓可视大屏寻呼台 兼容标准sip协议 A32i 是专门针对行业用户需求研发的一款可视大屏寻呼台产品&#xff0c;配备鹅颈麦克风&#xff0c;支持高清免提通话。基于 Android 9.0 系统&#xff0c;可支持第三方Android 应用安装使用&#xff0c;界面使用便捷。采用 10.1 英…

大模型与数据库:AI 时代的双向助推力

随着 AIGC 的时代到来&#xff0c;以 GPT 为首的大型语言模型&#xff08;Large Language Model&#xff0c;LLM&#xff09;已经成为当今人工智能领域最热门的话题之一。这些强大的模型不仅在内容创意生成、语言翻译和代码辅助等任务中表现出色&#xff0c;还对数据库的发展带…

DatenLord前沿技术分享 NO.35

达坦科技专注于打造新一代开源跨云存储平台DatenLord&#xff0c;通过软硬件深度融合的方式打通云云壁垒&#xff0c;致力于解决多云架构、多数据中心场景下异构存储、数据统一管理需求等问题&#xff0c;以满足不同行业客户对海量数据跨云、跨数据中心高性能访问的需求。在本周…

通过机器视觉对硬盘容器上盖的字符进行视觉识别,判断是否混料

 客户的需求  检测内容 硬盘容器上盖字符识别&#xff0c;以判断是否有混料。  检测要求 利用硬盘容器上盖表面字符&#xff0c;来判断是否有混料的情况发生&#xff0c;先识别全部字符。  视觉可行性分析 对贵司的样品进行了光学实验&#xff0c;并进行…

【操作系统】进程,线程和协程的哪些事儿

进程&#xff0c;线程和协程的哪些事儿 进程什么是进程?进程的状态进程的控制结构 线程为什么使用线程&#xff1f;什么是线程&#xff1f;线程与进程的比较线程的实现用户级线程内核级线程轻量级进程 协程协程是什么&#xff1f;协程的优势 区别进程与线程的区别协程与线程的…

【Spatial-Temporal Action Localization(三)】论文阅读2018年

文章目录 1. AVA: A Video Dataset of Spatio-temporally Localized Atomic Visual Actions 时空局部原子视觉动作的视频数据集摘要和结论模型框架思考不足之处时间信息对于识别 AVA 类别有多重要&#xff1f;定位与识别相比有何挑战性&#xff1f;哪些类别具有挑战性&#xff…

Python console cmd命令乱码(无论是os还是subprocess)

给我整无语了&#xff0c;花了一个多小时&#xff0c;根本没找到需要的答案。 网上全是改这样的 五花八门都有&#xff0c;我全部尝试并且还就再排列组合修改&#xff0c;累的。 在下文找到答案&#xff0c;直接os.system(chcp 65001)&#xff0c;问题解决&#xff01;引用文献…

IEC 61850扫盲

目录 1 简介 2 主要特点 2.1 信息分层 2.2 信息模型与通信协议独立 2.3 数据自描述 2.4 面向对象数据统一建模 2.5 带确认服务 2.6 不带确认的服务 2.7 VMD&#xff08;虚拟制造设备&#xff09; 2.8 GOOSE&#xff08;Generic Object Oriented Substation Event&…

LeetCode(力扣)435. 无重叠区间Python

LeetCode435. 无重叠区间 题目链接代码 题目链接 https://leetcode.cn/problems/non-overlapping-intervals/ 代码 class Solution:def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:if not intervals:return 0intervals.sort(keylambda x: x[0])co…

ajax day4

1、promise链式调用 /*** 目标&#xff1a;把回调函数嵌套代码&#xff0c;改成Promise链式调用结构* 需求&#xff1a;获取默认第一个省&#xff0c;第一个市&#xff0c;第一个地区并展示在下拉菜单中*/let pname axios({url: http://hmajax.itheima.net/api/province,}).t…