(JAVA)2-3树思想与红黑树的实现与基本原理

news2024/10/12 20:23:06

1. 平衡树

​ 学习过二叉查找树,发现它的查询效率比单纯的链表和数组的查询效率要高很多。

​ 大部分情况下确实是这样的,但不幸的是,在最坏情况下,二叉查找树的性能还是很糟糕。

例如我们一次往二叉树中插入9,8,7,6,5,4,3,2,1这9个数据,那么最终构造出来的树长得是下面这个样子

在这里插入图片描述

我们会发现,如果我们要查询1这个元素,查询的效率依旧很低,效率低的原因在于这个树并不平衡,全都是向左分支,如果我们有一种方法能够不受插入数据的影响,让生成的树都像完全二叉树那样,那么即使在最坏情况下,查找的效率依旧会很好。

2. 2-3查找树

一颗2-3查找树要么为空,要么满足下面两个要求:

  • 2-节点
    • 含有一个键(及其对应的值)和两条链,
      • 左链接指向2-3树中的键都小于该节点,
      • 右链接指向的2-3树中的键都大于该节点
  • 3-节点
    • 含有两个键(及其对应的值)和三条链,
      • 左链接指向2-3树中的键都小于该节点,
      • 中链接指向的2-3树中的键都位于该节点的两个键之间,
      • 右链接指向的2-3树中的键都大于该节点

在这里插入图片描述

2.1 查找

​ 将二叉查找树的查找算法一般化,我们就能够直接得到2-3树的查找算法。

​ 要判断一个键是否在树中,我们先将它和根节点中的键比较。

​ 如果它和其中一个键相等,查找命中;否则我们就根据比较的结果找到指向相应区间的链接,并在其指向的子树中递归地继续查找。如果这个是空链接,查找未命中。

在这里插入图片描述
在这里插入图片描述

2.2 插入

2.2.1 向2-节点中插入新键

​ 往2-3树种插入元素和往二叉查找树中插入元素一样,首先要进行查找,任何及那个节点挂到未找到的节点上。2-3树之所以能够保证在最差的情况下的效率的原因在于其插入之后仍然能够保持平衡状态。

​ 如果查找后未找到的节点是一个2-节点,那么很容易,我们只需要将新的元素放到这个2-节点里面使其变成一个3-节点即可。但是如果查找的节点结束于一个3-节点,那么可能有点麻烦

在这里插入图片描述

2.2.2 向一颗只含一个3-节点的树中插入新键

​ 假设2-3树只包含一个3-节点,这个节点有两个节点,没有空间来插入第三个键了,最自然的方式是它们假设这个节点能存放3个元素,暂时使其变成一个4-节点,同时它包含四条链接。然后,我们将这个4-节点的中间元素提升,左边的键作为其左子节点,右边的键作为其右子节点。插入完成,变为平衡2-3查找树,树的高度从0变为1

在这里插入图片描述

2.2.3 向一个父节点为2-节点的3-节点中插入新键

​ 和上面的情况一样,我们也可以将新的元素插入到3-节点中,使其成员一个临时的4-节点,然后将该节点的中间元素提升到父节点即2-节点中,使其父节点成为一个3-节点,然后将左右节点分别挂在这个3-节点的恰当位置
在这里插入图片描述在这里插入图片描述

2.2.4 向一个父节点为3-节点的3-节点中插入新键

​ 当我们插入的节点是3-节点的时候,我们将该节点拆分,中间元素提升至父节点,但是此时父节点是一个3-节点,插入之后,父节点变成了4-节点,然后继续将中间元素提升至其父节点,直至遇到一个父节点是2-节点,然后将其变为3-节点,不需要继续进行拆分

在这里插入图片描述
在这里插入图片描述

2.2.5 分解根节点

​ 当插入节点到根节点的路径上全部都是3-节点的时候,最终我们的根节点会变成一个临时的4-节点,此时,就需要将根节点拆分为2个2-节点,树的高度如下

在这里插入图片描述
在这里插入图片描述

2.4 2-3树的性质

​ 通过对2-3树插入操作的分析,我们发现在插入的时候,2-3树需要做一些局部的变换来保持2-3树的平衡

一颗完全平衡的2-3树具有以下性质:

  • 任意空链接到根节点的路径长度都是相等的。
  • 4-节点变换为3-节点时,树的高度不会发生变化,只有当根节点时临时的4-节点,分解根节点时,树的高度才会+1
  • 2-3树于普通二叉查找树的最大的区别在于,普通的二叉查找树是自定向下生长,而2-3树是自底向上生长

2.5 2-3树的实现

直接实现2-3树比较复杂,因为:

  • 需要处理不同的节点类型,非常繁琐;
  • 需要多次比较操作来将节点下移
  • 需要上移来拆分4-节点
  • 拆分4-节点的情况有很多种

2-3查找树实现起来比较比较复杂,在某些情况插入后的平衡操作可能会使得效率降低。但是2-3查找树作为一种比较重要的概念和思路对于红黑树、B树和B-树非常重要

3. 红黑树

3.1 红黑树的概述

  • 平衡树中的一种,基于二叉树,实现思想来自于2-3树

在2-3树的实现原理中,可以看到2-3树能保证在插入元素后,树依然保持平衡状态。

它的最坏情况下所有子节点都是2-节点,树的高度为lgN,

相比于我们普通的二叉查找树,最坏情况下树的高度为N,确实保证了最坏情况下的时间复杂度,

但是2-3树实现起来过于复杂,所以下面将学习**基于2-3树思想实现的红黑树**

红黑树主要是对2-3树进行编码,红黑树背后的基本思想使用标准的二叉查找树(完全由2-节点构成)和一些额外的信息(替换3-节点)来表示2-3树。将红黑树中的链接分为两种类型:

  • 红链接:
    • 将两个2-节点连接起来构成一个3-节点
  • 黑链接:
    • 2-3树中的普通链接

确切的说,我们将3-节点表示为由一条左斜的红色链接(两个2-节点其中之一是另一个左子节点)相连的两个2-节点。这种表示法的一个优点是:我们无需修改就可以直接使用标准的二叉查找树的get方法

在这里插入图片描述

3.2 红黑树的定义

红黑树是含有红黑链接并满足下列条件的二叉查找树:

  1. 红链接均为左链接
  2. 没有任何一个节点同时和两条红链接相连
  3. 概述是完美黑色平衡的,即任意空链接到根节点的路径上的黑链接数量相同

下面是红黑树于2-3树的对应关系:

在这里插入图片描述在这里插入图片描述

3.3 红黑树的节点API

因为每个节点都只会由一条指向自己的链接(从它的父节点指向它),我们可以在之前的Node节点中添加一个布尔类型的遍历color来表示链接的颜色。

如果指向它的链接是红色的,那么该变量的值为true,如果链接是黑色的,那么该变量的值为false
在这里插入图片描述

3.3.1 节点类API设计

类名Node<Key,Value>
构造方法Node(Key key,Value value,Node left,Node right,boolean color):创建Node对象
成员变量1. public Node left:记录左子节点
2. public Node right:记录右子节点
3. public Key key:存储键
4. public Value value:存储值
5. public boolean color:由其父节点指向它的链接的颜色

3.4 平衡化

在对红黑树进行一些增删查改的操作后,很有可能会出现红色的有链接或者两条连续红色的链接,而这些都不符合红黑树的定义(详看1.2),所以我们需要对这些情况通过旋转来进行修复,让红黑树保持平衡。

3.4.1 左旋

当某个节点的左子节点为黑色,右子节点为红色,此时需要左旋

**前提:**当前节点为h,它的右子节点为x;

左旋过程:
  1. 让x的左子节点变为h的右子节点:h.right = x.left;
  2. 让h成为x的左子节点:x.left = h;
  3. 让h的color属性变为x的color属性值:x.color = h.color;
  4. 让h的color属性变为RED:h.color = true;

在这里插入图片描述

3.4.2 右旋

在某个节点的左子节点是红色,且左子节点也是红色,需要节点

**前提:**当前节点为h,它的左子节点为x

右旋过程:
  1. 让x的右子节点成为h的左子节点:h.left = x.right;
  2. 让h成为x的右子节点:x.right = h;
  3. 让x的color变为h的color属性值:x.color = h.color;
  4. 让h的color为RED

在这里插入图片描述

3.5 插入

3.5.1 向单个2-节点中插入新键

一颗只含有一个键的红黑树只含有一个2-节点。插入另一个键后,我们马上就需要将它们旋转。

  • 如果新键小于当前节点的键,我们只需要新增一个红色节点即可,新的红黑树和单个3-节点完全等价。

在这里插入图片描述

  • 如果新键大于当前节点的键,那么新增的红色节点将会产生一条红色的有链接,此时我们需要通过左旋,把红色右链接变成做左链接,插入操作才算完成。形成的新红黑树依然和3-节点等价,其中含有两个键,一条红色链接。

在这里插入图片描述

3.5.2 向底部的2-节点插入新键

用和二叉查找树相同的方向向一颗红黑树中插入一个新键,会在树的底部新增一个节点(可以保证有序性),唯一区别的地方是我们会用红链接将新节点和它的父节点相连。如果它的父节点是一个2-节点,那么刚才讨论的两种方式仍然适用。

在这里插入图片描述

3.5.3 颜色反转

当一个节点的左子节点和右子节点的color都为RED时,也就是出现了临时的4-节点,此时直选哟把左子节点和右子节点的颜色变为BLACK,同时让当前节点的颜色变为RED即可。

在这里插入图片描述

3.5.4 向一颗双键树(3-节点)中插入新键

这种情况还可以有三种子情况:

  1. 新键大于原树种的两个键:

    在这里插入图片描述

  2. 新键小于原树中的两个键:

    在这里插入图片描述

  3. 新键介于原树种两个键之间:

在这里插入图片描述
在这里插入图片描述

3.5.5 根节点的颜色总是黑色

之前介绍节点API的时候,在节点Node对象中color属性表示的是父节点指向当前节点的连接的颜色,由于根节点不存在父节点,所以每次插入操作后,我们都需要把根节点的颜色设置为黑色。

3.5.6 向树底的3-节点插入新键

假设在树的底部的一个3-节点下加入一个新的节点(前面三种子情况都出现了)。指向新节点的链接可能是3-节点的右链接(此时转换颜色),或是左连接(此时需要右旋后再转换颜色),或是中连接(此时先左旋再右旋左后转换颜色)。

颜色转换会使中间节点的颜色变红,相当于将它送入了父节点。这意味着父节点中继续插入一个新键,我们只需要适用相同的方法解决即可,直到遇到一个2-节点或者根节点为止

在这里插入图片描述在这里插入图片描述
在这里插入图片描述

3.6 红黑树的API设计

在这里插入图片描述

3.7 红黑树的实现

package com.renexdemo.tree;

// 红黑树
public class RedBlackTree<Key extends Comparable<Key>,Value> {
    private Node root;
    private int N;
    private static final boolean RED = true;
    private static final boolean BLACK = false;

    // 节点类
    private class Node{
        public Key key;
        public  Value value;

        public Node left;
        public Node right;
        public boolean color;

        public Node(Key key, Value value, Node left, Node right, boolean color) {
            this.key = key;
            this.value = value;
            this.left = left;
            this.right = right;
            this.color = color;
        }
    }

    // 初始化红黑树
    public RedBlackTree() {
    }

    // 链接是否为红色
    private boolean isRed(Node x){
        if (x == null){
            return false;
        }
        return x.color == RED;
    }
    // 树的节点个数
    public int size(){
        return N;
    }

    // 左旋
    private Node rotateLeft(Node h){
        // 获取h节点的右子节点,表示为x
        Node x = h.right;

        // 让x节点的左子节点成为h节点的右子节点
        h.right = x.left;

        // 让h成为x节点的左子节点
        x.left = h;

        // 让x节点的color属性等于h节点的color属性
        x.color = h.color;

        // 让h节点的color属性变为红色
        h.color = RED;

        return x;
    }
    // 右旋
    private Node rotateRight(Node h){
        // 获取h节点的左子节点,表示为x
        Node x = h.left;

        // 让x节点的右子节点成为h节点的左子节点
        h.left = x.right;

        // 让h成为x节点的右子节点
        x.right = h;

        // 让x节点的color属性等于h节点的color属性
        x.color = h.color;

        // 让h节点的color属性变为红色
        h.color = RED;
        return x;
    }
    //颜色反转
    private void flipColors(Node h){
         // 当前节点变为红色
        h.color = RED;

        // 左子节点和右子节点变为黑色
        h.right.color = BLACK;
        h.left.color = BLACK;
    }

    // 添加节点
    public void put(Key key,Value val){
        root = put(root,key,val);
        root.color = BLACK;// 根节点的颜色总是黑色的
    }
    // 指定节点处添加节点
    private Node put(Node h,Key key,Value val){
        // 判断h是否为空
        if (h == null) {
            N++;
            return new Node(key,val,null,null,RED);
        }

        // 比较h节点的键和k的大小
        int cmp = key.compareTo(h.key);
        if (cmp < 0) {
            // 继续往左
            h.left = put(h.left, key, val);

        } else if (cmp > 0) {
            // 继续往右
            h.right = put(h.right, key, val);

        }else {
            // 值替换
            h.value = val;
        }

        // 进行左旋;当当前节点节点h的左子节点为黑色,右子节点为红色,需要左旋
        if (isRed(h.right) && !isRed(h.left)){
            h = rotateLeft(h);
        }

        // 进行右旋:当当前节点h的左子节点和左子子节点都为红色,需要右旋
        if (isRed(h.left) && isRed(h.left.left))
        {
            h = rotateRight(h);
        }
        // 颜色反转:当前节点的左子节点和右子节点都为红色时,需要颜色反转
        if (isRed(h.left) && isRed(h.right)){
            flipColors(h);
        }

        return h;
    }
    // 获得对应键的值
    public Value get(Key key){
        return get(root,key);
    }
    // 获得指定节点处对应键的值
    private Value get(Node x,Key key){
        if (x==null){
            return null;
        }

        // 比较x的键和key的大小
        int cmp = key.compareTo(x.key);
        if (cmp<0){
            return get(x.left,key);
        }else if (cmp>0){
            return get(x.right,key);
        }else {
            return x.value;
        }
    }

}

4. 前置文章

  1. 浅入数据结构 “堆” - 实现和理论
  2. 开始熟悉 “二叉树” 的数据结构
  3. 队列 和 符号表 两种数据结构的实现
  4. 队列的进阶结构-优先队列

5. ES8 如何使用?

快来看看这篇好文章吧~~!!
😊👉(全篇详细讲解)ElasticSearch8.7 搭配 SpringDataElasticSearch5.1 的使用

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

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

相关文章

【LeetCode】动态规划—714. 买卖股票的最佳时机含手续费(附完整Python/C++代码)

动态规划—714. 买卖股票的最佳时机含手续费 题目描述前言基本思路1. 问题定义2. 理解问题和递推关系状态定义&#xff1a;状态转移公式&#xff1a;初始条件&#xff1a; 3. 解决方法动态规划方法伪代码&#xff1a; 4. 进一步优化5. 小总结 Python代码Python代码解释总结&…

出海电商新怎样用海外云手机引流?

随着互联网行业的迅猛发展&#xff0c;出海电商、海外社交媒体营销以及游戏产业等领域对技术工具的需求不断增加。在这种趋势下&#xff0c;海外云手机作为一种新型解决方案&#xff0c;正在受到广泛关注。 特别是在出海电商中&#xff0c;平台如亚马逊、速卖通、eBay等通过结合…

Mysql(八) --- 视图

文章目录 前言1.什么是视图&#xff1f;2.创建视图3. 使用视图4. 修改数据4.1.注意事项 5. 删除视图6.视图的优点 前言 前面我们学习了索引&#xff0c;这次我们来学习视图 1.什么是视图&#xff1f; 视图是一个虚拟的表&#xff0c;它是基于一个或多个基本表或其他视图的查询…

8款宝藏手机app,适配安卓和苹果手机

好用的手机APP太多&#xff0c;差点挑花了眼&#xff01;今天来分享4款苹果手机和4款安卓手机上的宝藏软件&#xff0c;看看你喜欢哪一款~ IOS系统APP 1.搜图神器 一款拥有海量图片资源的图片搜索神器&#xff0c;它聚合海内外知名搜索引擎&#xff0c;想要图片直接搜索就行…

用java来编写web界面

一、ssm框架整体目录架构 二、编写后端代码 1、编写实体层代码 实体层代码就是你的对象 entity package com.cv.entity;public class Apple {private Integer id;private String name;private Integer quantity;private Integer price;private Integer categoryId;public…

【JavaScript】LeetCode:61-65

文章目录 61 课程表62 实现Trie&#xff08;前缀树&#xff09;63 全排列64 子集65 电话号码的字母组合 61 课程表 Map BFS拓扑排序&#xff1a;将有向无环图转为线性顺序。遍历prerequisites&#xff1a;1. 数组记录每个节点的入度&#xff0c;2. 哈希表记录依赖关系。n 6&a…

Vulnhub靶场案例渗透[7]- DC7

文章目录 1. 靶场搭建2. 信息收集2.1 确定靶机ip2.2 服务信息收集2.3 社工信息收集 3. 提权 1. 靶场搭建 靶场源地址 检验下载文件的检验码&#xff0c;对比没问题使用vmware打开 # windwos 命令 Get-FileHash <filePath> -Algorithm MD5 # linux md5sum filepath2. 信…

视频汇聚平台EasyCVR支持云端录像丨监控存储丨录像回看丨录像计划丨录像配置

EasyCVR视频汇聚融合平台&#xff0c;是TSINGSEE青犀视频垂直深耕音视频流媒体技术、AI智能技术领域的杰出成果。平台以其强大的视频处理、汇聚与融合能力&#xff0c;在构建视频监控系统中展现出了独特的优势。 EasyCVR视频汇聚平台可接入传统监控行业中高清网络摄像机的RTSP…

提升实验室效率的秘籍

有组织、高效的实验室而言&#xff0c;业务“人、机、料、法、环、测”的多维度发展至关重要&#xff0c;为了提高实验室管理效率和质量&#xff0c;许多实验室开始采用LIMS&#xff08;实验室信息管理系统&#xff09;软件来辅助管理。LIMS软件能够帮助实验室实现信息化、自动…

leetcode 3217 从链表中移除在数组中的结点

1.题目要求: 给你一个整数数组 nums 和一个链表的头节点 head。从链表中移除所有存在于 nums 中的节点后&#xff0c;返回修改后的链表的头节点。 示例 1&#xff1a; 输入&#xff1a; nums [1,2,3], head [1,2,3,4,5] 输出&#xff1a; [4,5] 解释&#xff1a; 移除数值…

Java中的枚举

1.1 认识枚举 枚举是一种特殊的类&#xff0c;它的格式是&#xff1a; public enum 枚举类名{枚举项1,枚举项2,枚举项3; } 其实枚举项就表示枚举类的对象&#xff0c;只是这些对象在定义枚举类时就预先写好了&#xff0c;以后就只能用这几个固定的对象。 定义一个枚举类&am…

使用VS2015编写C语言程序

前面我们给出了一段完整的C语言代码&#xff0c;就是在显示器上输出“C语言中文网”&#xff0c;如下所示&#xff1a; #include <stdio.h>int main(){puts("C语言中文网");return 0;}本节我们就来看看如何通过 VS2015 来运行这段代码。 1) 创建项目&#xf…

QD1-P8 HTML 格式化标签(font、pre、b、strong、i、u、del、s、sub、sup)

本节学习&#xff1a;HTML 格式化标签。 本节视频 www.bilibili.com/video/BV1n64y1U7oj?p8 ‍ 一、font 标签 用途&#xff1a;定义文本的字体大小、颜色和 face&#xff08;字体类型&#xff09;。 示例 <!DOCTYPE html> <html><head><meta cha…

Tkinter:为什么多个Frame相互覆盖?

在 Tkinter 中&#xff0c;Frame 是一个容器部件&#xff0c;用于组织和管理布局。如果多个 Frame 出现在同一个父容器中并且看起来相互覆盖&#xff0c;通常与布局管理器的使用方式或控件的创建顺序有关。 以下是几个常见的原因和解决方案&#xff0c;帮助你了解为什么多个 F…

生产报工信息化全流程大讲解

在企业的生产管理中&#xff0c;生产报工是一个关键环节&#xff0c;但传统的生产报工方式存在诸多痛点&#xff0c;制约了企业的发展。随着数字化技术的发展&#xff0c;多个平台为企业提供了有效的解决方案。基于生产报工信息化方案报告》白皮书&#xff0c;本文深入探讨生产…

三菱FX3U PLC绝对定位- DRVA指令

指令格式 相关软元件一览 功能和动作 这是采用绝对驱动的单速定位指令。采用从原点(0点)开始的距离指定方式&#xff0c; 也被称为绝对驱动方式。 1、在指令执行过程中&#xff0c;即使改变操作数的内容&#xff0c;也不反映到当前的运行中。 在下次的指令驱动时才有效…

客户服务的未来趋势:智能化与人性化的融合

在当今这个日新月异的数字时代&#xff0c;企业的竞争已不再局限于产品或服务的本身&#xff0c;而是延伸到了客户体验的每一个细微之处。数字化转型作为推动这一变革的重要力量&#xff0c;正深刻改变着客户服务的面貌&#xff0c;使之变得更加智能、便捷且充满人性化。随着人…

最长回文子串-双下标动态规划

题目来源&#xff1a;Leetcode 5.最长回文子串 DP定义&#xff1a; 容易想到&#xff0c;用一个二维数字dp[i][j]来表示s[i:j]是否是回文串&#xff0c;如s“daba”。dp[1][3]1表示"aba"为回文串&#xff1b; 递归条件 想要判断字符串"aba"是否为回文…

MySQL--事务(详解)

目录 一、前言二、本文章目标三、什么是事务&#xff1f;四、事务的ACID特性五、为什么要使用事务六、如何使用事务6.1 查看支持使用事务的引擎6.2语法6.3 开启⼀个事务&#xff0c;执行更新后回滚6.4 开启一个事务更新后提交6.5 保存点6.6 自动/手动提交事务 七、事务的隔离性…

X86、ARM架构镜像

1. 简介 ARM 镜像和 x86 镜像是为不同处理器架构设计的软件镜像。ARM&#xff08;Advanced RISC Machine&#xff09;架构和 x86 架构是两种主流的处理器指令集架构&#xff0c;它们在设计和性能特点上有所不同。以下是 ARM 镜像和 x86 镜像的一些主要区别&#xff1a; 处理器架…