二叉搜索树、红黑树详解、红黑树高的应用、TreeMap的应用(图文详解)-Kotlin版本代码

news2025/7/12 17:06:49

二叉搜索树

何为二叉搜索树?

二叉搜索树是一种特殊的二叉树,它的左子节点总是小于或等于根节点,而右子节点 总是大于或等于根节点。

如下图,即是一颗二叉搜索树。
在这里插入图片描述

对于二叉搜索树来说,中序遍历可以遍历按照节点值递增的顺序遍历二叉搜索树的每个节点。比如上述中序遍历的顺序为节点4、节点5、节点6、节点7、节点8、节点9和节点10。

查找

对于普通的二叉树来说,查找一个值需要遍历整颗二叉树,因此需要O(n)的时间。但是对于二叉搜索树来说,则完全不需要如此麻烦,如果当前节点小于要查找的值,则往其右节点继续查找;如果当前节点大于要查找的值,则往其左节点继续查找。如此重复直到找到即可,那么此时搜索时间复杂度仅仅是二叉树的高度h,即O(h)。一个高度平衡的二叉搜索树的高度近似可以看作为log(n)

代码:二叉搜索树如何查找一个值。

fun binarySearchTreeFind(node: TreeNode1, value: Int): TreeNode1? {
    var currentNode: TreeNode1? = node;
    while (currentNode != null) {
        if (currentNode.value == value) {
            break
        }
        if (currentNode.value > value) {
            currentNode = currentNode.leftNode
        } else {
            currentNode = currentNode.rightNode
        }
    }
    return currentNode
}
//使用
binarySearchTreeFind(Helper.createBinarySearchTree(),9).let {
    println("value is ${it?.value?:"is null"}")
}
//结果
value is 9

红黑树(自平衡二叉树)

为什么要有红黑树?

从上我们知道二叉搜索树是一种很有用的数据结构,如果深度为H,则增删查的时间复杂度均为O(H)。如果二叉搜索树是平衡的,那么其深度近似等于logn。但是在极端情况下,二叉搜索树均只有一个节点,那么其高度则为n-1,此时时间复杂度就变为O(n)。所以二叉搜索树是否平衡很重要。

java则是根据红黑树这种平衡而二叉搜索树实现了TreeSetTreeMap这两种数据结构。所以我们有必要了解一下红黑树。

红黑树是一种特殊的二叉查找树,结构如其名,每个结点都要储存位表示结点的颜色,或

红黑树需要遵守如下几个特性:

  1. 每个节点或红或黑

  2. 根节点是黑色

  3. 空叶子节点是黑色

  4. 如果一个节点是红色,那么其子节点是黑色

  5. 从任意一个节点出发到空的叶子节点经过的黑节点个数相同

    从3和5又可以推出:
    5.1: 如果一个节点存在黑子节点,那么该结点肯定有两个子节点。

在这里插入图片描述

只要遵守上述规则就可以了,而对于增加、删除时会破坏红黑树的规则,其中最主要的破坏的是以下两点:

  1. 如果一个节点是红色,那么其子节点是黑色,红色节点是被黑色节点隔开的
  2. 从任意一个节点出发到空的叶子节点经过的黑节点个数相同

显而易见,如果破坏规则了,我们就是需要按照规则还原即可。

如何还原呢?通过变色、左旋以及右旋,变色即为将节点的颜色由红色变为黑色或者由黑色变为红色。除此之外还需要了解左旋以及优选。

基本操作之旋转

旋转操作仅仅只是用来调节结点的位置的,就是为了满足红黑树的性质5。

左旋

以某个结点作为支点(旋转结点),其右子结点变为旋转结点的父节点,右子结点的左子结点变为旋转结点的右子结点,左子结点保持不变。

在这里插入图片描述

右旋

以某个结点作为支点(旋转结点),其左子结点变为旋转结点的父节点,左子结点的右子结点变为旋转结点的左子结点,右子结点保持不变。

在这里插入图片描述

红黑树基本操作

元素添加

操作

当作二叉搜索树一样插入节点,将节点赋为红色,判断如果不符合要求则通过旋转或者着色的方式使之重新成为一颗二叉树。

为什么将新加入的节点赋值为红色呢?

  1. 不违背性质5,从任意一个节点出发到空叶子节点经过的黑色节点个数相同。
  2. 根据性质4,可知黑色节点数至少是红色节点数的两倍,故,直接插入红色节点其父节点是黑色的可能性比较大,此时不需要进行额外进行调整。

我们现在在分析一下之前的性质,思考插入一个红色节点会违背上述那几个约定呢?很明显1、2、3、5都不会违背,只有4有可能违背。

下面具体分析一下插入的情况

  • 插入的节点为根节点

    直接将红色赋值为黑色

  • 父节点为黑色节点

    不需要进行任何操作

  • 父节点为红色节点的情况下,叔叔节点为红色节点

    • 将叔叔和父亲结点改为黑色,爷爷结点改为红色。继续将爷爷结点当作插入结点看待,重复之前的操作,直到当前结点为根结点,然后将根结点变成黑色。
  • 父节点为红色节点的情况下,叔叔节点为黑色节点

    • 父亲结点为爷爷结点的左孩子,新插入结点为父节点的左孩子(左左情况)

    • 父亲结点为爷爷结点的右孩子,新插入结点为父亲结点的右孩子(右右情况)

      以上两种情况,均进行这样处理:将父亲结点和爷爷结点的颜色互换,然后针对爷爷结点进行一次左旋

    • 插入结点是右结点,父节点是左结点

    • 插入结点是左结点,父亲结点是右结点

      以上两种情况,均进行这样处理:针对父结点进行左旋,此时左旋后的情况是左左或者右右的情况,然后按照左左或者右右的的情况处理。

元素删除

操作

将红黑树作为二叉搜索树进行查找,找到后将该节点从树中删除,然后通过着色、旋转等操作使其重新成为一颗红黑树。

删除操作涉及到要删除的节点是否存在子节点,所以可以分为以下三个大的情况:

  1. 要删除的节点没有子节点
  2. 要删除的节点存在一个子节点
  3. 要删除的节点存在两个子节点

首先看,要删除的节点没有子节点

  1. 删除的节点为红色节点

    直接删除即可

  2. 删除节点为黑色,其兄弟节点没有儿子。

    兄弟节点变红,父亲节点变黑,然后将父亲节点当作当前节点目前的这几种情形处理,直至到根节点。

  3. 删除节点为黑色,兄弟节点有一个孩子不空,并且该孩子和兄弟节点在同一边(同为左子树或者同为右子树):

    交换兄弟节点和父亲节点的颜色,并且把父亲节点和兄弟节点的子结点涂成黑色,之后进行如下操作

    1. 兄弟节点和兄弟节点的子节点都在右子树;对兄弟节点进行左旋
    2. 兄弟节点和兄弟节点的子节点都在左子树;对兄弟节点进行右旋
  4. 删除节点是黑色,兄弟节点有一个不空,该孩子和兄弟不在同一边

    1. 将兄弟节点和兄弟节点的子节点颜色进行互换
    2. 兄弟节点是左子树,兄弟节点的子节点是右子树;对兄弟节点进行左旋
    3. 兄弟节点是右子树,兄弟节点的子节点是左子树;对兄弟节点进行右旋
    4. 按照3处理
  5. 删除节点为黑色,兄弟节点两个孩子(空节点也算作子节点),且颜色均为黑色

    父节点和兄弟节点颜色互换

  6. 删除节点为黑色,兄弟节点有两个孩子,且兄弟节点两个孩子为红色。

    1. 父节点和兄弟节点颜色互换
    2. 被删除的元素为左节点,对父节点进行左旋
    3. 被删除的元素为右节点,对父节点进行右旋
    4. 按照5处理

下面讨论,删除的节点存在一个子节点的情况

  1. 删除节点为黑色,儿子节点无论左右位置

    将子节点涂为黑色,放到被删除节点的位置

  2. 不存在被删除节点为红色节点的情况,因为这种情况不满足性质5

最后讨论,删除的节点存在两个孩子的情况

找到删除节点的右子树中最左的节点,两两交换,然后删除节点的情况就变成了下面的情况之一了,然后进行相关的处理即可。

1.删除节点只有一个儿子的情况

2.删除节点没有儿子的情况

红黑树高度上线证明推导

从上面知道红黑树的增删查时间复杂度均为O(H)。

那么其高度H近似等于多少呢?这里列出证明

首先结论是:h≤2log2(n+1)

证明:

我们规定,从任意节点出发,到其子树的子叶节点的路径中黑色节点的数量称为该节点的黑高,即为bh

设:根节点为T,则根节点的黑高则为bh(T)

根据红黑树的性质,可知红色节点不可以相邻,但是没有规定黑色节点不可以相邻,所以可以假设极端情况下一颗红黑树均为黑色节点。那么此时我们可以得出,这样一棵树的节点数n和树高的关系为n=2^bh(T)-1

但是对于红黑树而言会包含红色节点,所以一定有n≥2^bh(T)-1(1)。

另外根据红黑树的性质,黑高至少为树高的二分之一,即bh(T)≥h/2(2)。

则根据(1)式和(2)式可得n≥2^bh(T)-1≥2^(h/2)-1n≥2^(h/2)-1

然后我们推导一下:

  1. 2^(h/2)≤n+1
  2. log2^(n+1)≥h/2
  3. h≤2log2(n+1)

所以最终可得:h≤2log2(n+1)

JAVA TreeMap的简单应用(基于红黑树)

Java根据红黑树这种平衡的二叉搜索树实现TreeSetTreeMap结构。

TreeSet常用的函数:

序号函数函数功能
1ceiling返回大于或等于给定值的最小值,没有返回null
2floor返回小于或等于给定值的最大值,没有返回null
3higher返回大于给定值的最小值,没有返回null
4lower返回小于给定值的最大值,没有返回null

TreeMap常用的函数:

序号函数函数功能
1ceilingEntry/ceilingKey返回大于或等于给定值的最小映射/值,没有返回null
2floorEntry/floorKey返回小于或等于给定值的最大映射/值,没有返回null
3higherEntry/higherKey返回大于给定值的最小映射/值,没有返回null
4lowerEntry/lowerKey返回小于给定值的最大映射/值,没有返回null

接下来写一个例子,利用TreeMap实现日程表的功能:

题目如下

请实现一个类型MyCalendar用来记录自己的日程安排,该类型用方法book(int start,int end)在日程表中添加一个时间区域为[start,end)的事项(这是一个半开半闭区间)。如果[start,end)中之前没有安排其他事项,则成功添加该事项并返回true;否则,不能添加该事项,并返回false。

比如:下面的3次调用book方法中,第2次调用返回false,这是因为时间[15,20)已经被第1次调用预留了。由于第1次占用的时间是一个半开半闭区间,并没有真正占用时间20,因此不影响第3次调用预留时间区间[20,30)。

    val calendar = MyCalendar()
    println(calendar.book(10, 20))
    println(calendar.book(15, 25))
    println(calendar.book(20, 30))
    //结果
    true
	false
	true

题目分析

如果待添加的事项占用的时间区间是[m,n),就需要找出开始时间小于m的所有事项中开始最晚的一个,以及开始时间大于m的所有事项中开始最早的一个。如果待添加的事项和这两个事项都没有重叠,那么该事项可以添加在日程表中。

代码

class MyCalendar {

    /**
     * key : start
     * value : end
     */
    private val events = TreeMap<Int, Int>()

    fun book(start: Int, end: Int): Boolean {

        val event = events.floorEntry(start)
        if (event != null && event.value > start) {
            return false
        }

        val event2 = events.ceilingEntry(start)
        if (event2 != null && event2.key < end) {
            return false
        }

        //存入
        events[start] = end
        return true
    }
}

🙆‍♀️。欢迎技术探讨噢!

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

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

相关文章

【JavaSE】重载和重写

前言&#xff1a; 作者简介&#xff1a;爱吃大白菜1132 人生格言:纸上得来终觉浅&#xff0c;绝知此事要躬行 如果文章知识点有错误的地方不吝赐教&#xff0c;和大家一起学习&#xff0c;一起进步&#xff01; 如果觉得博主文章还不错的话&#xff0c;希望三连支持&#xff01…

python--敲击木鱼积累功德小项目(更新版(2))

前言&#xff1a;前几天上课闲着没事写了一个python敲击木鱼积累功德的小项目&#xff0c;当时纯粹就是写着玩&#xff0c;回顾一下鼠标事件的东西还记不记得&#xff0c;发现这个博客的点赞和收藏量还挺高的&#xff0c;我当时也没有把它当回事&#xff0c;后面也有很多人问怎…

11.11一些资源整理和总结

使用python读取tensorboard文件中的数据并写入到excel当中去能够代替Originlab的画图软件&#xff08;macos&#xff09;Mac款origin来了&#xff01;还不来看看&#xff01;ptflops&#xff1a;计算网络参数FLOPs的工具[github] 4 中方式计算 FLOPs&#xff08;知乎&#xff0…

栈和队列实现的思路和代码

栈和队列第一节----栈什么是栈实现栈的基本思路各个接口函数的实现初始化栈销毁栈压栈出栈返回栈顶元素栈的判空栈的大小第二节----队列什么是队列实现队列的基本思路各个接口函数的实现队列的初始化队列的销毁队列的插入队列的删除返回队头元素和队尾元素队列的判空队列的大小…

使用DESeq2进行转录组原始count标准化和差异分析

转录组测序完成后&#xff0c;一般我们会获得一个原始 read count表达矩阵&#xff0c;其中行是基因&#xff0c;列是样品。常用的差异分析工具包括limma、edgeR和DESeq2。DESeq2在测序领域使用最为广泛&#xff08;google scholar引用高达43284次&#xff0c;edgeR为28076次&a…

MYSQL索引查询问题质疑

前言 我们在写mysql查询语句的时候&#xff0c;尤其是经验不足的同学肯定会想要怎么使用索引加快查询&#xff0c;或是我这样写到底会不会命中索引。那么现在我就列举几个常见的索引查询问题进行简单说明一下。&#xff08;欢迎互怼&#xff01;&#xff09; 1.问&#xff1a…

小学生python游戏编程arcade----游戏界面按钮实现事件实现的三种方法

小学生python游戏编程arcade----游戏界面按钮实现事件实现的三种方法前言游戏界面按钮实现事件实现的三种方法1、按钮定义及事件3种方法1.1 按钮定义1.2三种事件引用方法1.2.1 类法1.2.2 事件方法2,自定义方法函数1.2.3 事件方法3,使用装饰器处理onclick事件1.3 效果图1.4 代码…

第十四届蓝桥杯校模拟赛详解+代码(一)

“须知少时凌云志&#xff0c;自许人间第一流” 鄙人11月八号有幸参加学校蓝桥杯校选拔赛&#xff0c;题型为5道填空题&#xff0c;5道编程题&#xff0c;总时间为4小时。奈何能力有限&#xff0c;只完成了5道填空和3道编程大题&#xff0c;现进行自省自纠&#xff0c;分享学习…

艾美捷细胞计数试剂盒-8(CCK-8),一步到位

艾美捷细胞计数试剂盒-8&#xff08;CCK-8&#xff09;&#xff1a;用于简单准确的细胞增殖和细胞毒性测定的比色试剂盒。 艾美捷细胞计数试剂盒-8&#xff08;CCK-8&#xff09;特点&#xff1a; 1、一步到位&#xff0c;不含放射性同位素的即用溶液 2、与[3H]-胸苷掺入试验…

分销商城平台哪个好_分享分销商城开发步骤

微信分销商城平台分为两种&#xff1a; 第一种是入驻到别人的微信分销商城平台里面&#xff0c;帮助分销别人发布的商品从而获得佣金。 第二种情况是自己公司开发的微信分销商城平台&#xff0c;然后招募分销者进行分销公司的产品。 对于大家来讲肯定是自己公司开发的微信分销商…

数据结构与算法_大数据处理_求topK的两种求解方法

这篇笔记记录求大数据topk的两种方法&#xff0c;分别是大小二叉堆法和快速分割法&#xff0c;下面依次详解这两种方法的过程。 1 大/小根堆法 利用大根堆过滤前top k小的数据**&#xff1b;小根堆过滤前top k大的数据**&#xff1b; 下面用大根堆求前k个小元素为例。 思想&…

Python 算法:学习二分法

二分法&#xff0c;一个看似简单&#xff0c;逻辑易懂的算法&#xff0c;但是初次接触可能会有很多坑&#xff01;主要是边界处理的问题。 下面以一个耳熟能详的案例来展开&#xff1a; 案例描述&#xff1a; 小B从1~100之间&#xff08;含边界值&#xff09;任意想一个数字&a…

Spring Security 在登录时如何添加图形验证码

前言 在前面的几篇文章中&#xff0c;登录时都是使用用户名 密码进行登录的&#xff0c;但是在实际项目当中&#xff0c;登录时&#xff0c;还需要输入图形验证码。那如何在 Spring Security 现有的认证体系中&#xff0c;加入自己的认证逻辑呢&#xff1f;这就是本文的内容&…

matplotlib简介

matplotlib是一款用于画图的软件&#xff0c;以下步骤建议在.ipynb中完成。 导包 你需要导入以下包&#xff1a; import matplotlib as mpl import matplotlib.pyplot as plt import numpy as np一个简单案例 matplotlib 在 Figure上绘制图形&#xff0c;每一个Figure会包含…

【附源码】Python计算机毕业设计手游账号交易系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

Java注解式开发

目录 1. ssm框架的搭建 1.1 maven项目的创建 1.2 导入所需的包到pom.xml文件中 1.3 导入数据库连接文件、日志文件、redis连接文件 1.3.1 数据库连接文件 1.3.2 日志文件 1.3.3 redis连接文件 1.4 导入spring-mybatis、spring-mvc、spring-base、spring-redis四种集成文…

改变自己 只需要两年

改变自己 只需要两年 https://v.douyin.com/rLDmdQK/ 可以快速浏览上面视频 今天分享的这篇文章是TED上的一篇演讲 希望对下定决心想改变的你一些帮助。 用两年时间证明你可以 两年时间不算多长&#xff0c;但与此同时&#xff0c;很多事情都能在两年内完成&#xff0c;你…

WinHex(三)

目录 一、新建简单卷 二、MBR作用与结构 一、新建简单卷 1.右键点击刚刚新建的虚拟磁盘&#xff0c;选择新建简单卷。我新建了两个一个是NTFS&#xff0c;一个是FAT32 2.我们在刚刚新建的虚拟磁盘中放入一张图片&#xff0c;打开WinHex,点击“打开磁盘”选项&#xff0c;打…

[野火]STM32 F103 HAL库开发实战指南笔记之简单外设总结

1、GPIO编程总结 使能 GPIO 端口时钟&#xff1b;初始化 GPIO 目标引脚为推挽输出模式&#xff1b;编写简单测试程序&#xff0c;控制 GPIO 引脚输出高、低电平。 这部分宏控制 LED 亮灭的操作是直接向 BSRR 寄存器写入控制指令来实现的&#xff0c;对 BSRR 低 16 位 写 1 输出…

大学生静态HTML网页源码 我的校园网页设计成品 学校班级网页制作模板 web课程设计 dreamweaver网页作业

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…