【数据结构与算法】(14)基础算法 之AVL 树相关示例 详细代码讲解

目录

    • 3.4 红黑树
      • 概述
        • 历史
        • 红黑树特性
      • 实现
        • 插入情况
        • 删除情况
        • 完整代码
        • 小结

在这里插入图片描述

3.4 红黑树

概述

历史

红黑树是一种自平衡二叉查找树,最早由一位名叫Rudolf Bayer的德国计算机科学家于1972年发明。然而,最初的树形结构不是现在的红黑树,而是一种称为B树的结构,它是一种多叉树,可用于在磁盘上存储大量数据。

在1980年代早期,计算机科学家Leonard Adleman和Daniel Sleator推广了红黑树,并证明了它的自平衡性和高效性。从那时起,红黑树成为了最流行的自平衡二叉查找树之一,并被广泛应用于许多领域,如编译器、操作系统、数据库等。

红黑树的名字来源于红色节点和黑色节点的交替出现,它们的颜色是用来维护树的平衡性的关键。它们的颜色具有特殊的意义,黑色节点代表普通节点,而红色节点代表一个新添加的节点,它们必须满足一些特定的规则才能维持树的平衡性。

红黑树也是一种自平衡的二叉搜索树,较之 AVL,插入和删除时旋转次数更少

红黑树特性
  1. 所有节点都有两种颜色:红🔴、黑⚫️
  2. 所有 null 视为黑色⚫️
  3. 红色🔴节点不能相邻
  4. 根节点是黑色⚫️
  5. 从根到任意一个叶子节点,路径中的黑色⚫️节点数一样

实现

插入情况

插入节点均视为红色🔴

case 1:插入节点为根节点,将根节点变黑⚫️

case 2:插入节点的父亲若为黑色⚫️,树的红黑性质不变,无需调整

插入节点的父亲为红色🔴,触发红红相邻

case 3:叔叔为红色🔴

  • 父亲变为黑色⚫️,为了保证黑色平衡,连带的叔叔也变为黑色⚫️

  • 祖父如果是黑色不变,会造成这颗子树黑色过多,因此祖父节点变为红色🔴

  • 祖父如果变成红色,可能会接着触发红红相邻,因此对将祖父进行递归调整

case 4:叔叔为黑色⚫️

  1. 父亲为左孩子,插入节点也是左孩子,此时即 LL 不平衡
    • 让父亲变黑⚫️,为了保证这颗子树黑色不变,将祖父变成红🔴,但叔叔子树少了一个黑色
    • 祖父右旋,补齐一个黑色给叔叔,父亲旋转上去取代祖父,由于它是黑色,不会再次触发红红相邻
  2. 父亲为左孩子,插入节点是右孩子,此时即 LR 不平衡
    • 父亲左旋,变成 LL 情况,按 1. 来后续处理
  3. 父亲为右孩子,插入节点也是右孩子,此时即 RR 不平衡
    • 让父亲变黑⚫️,为了保证这颗子树黑色不变,将祖父变成红🔴,但叔叔子树少了一个黑色
    • 祖父左旋,补齐一个黑色给叔叔,父亲旋转上去取代祖父,由于它是黑色,不会再次触发红红相邻
  4. 父亲为右孩子,插入节点是左孩子,此时即 RL 不平衡
    • 父亲右旋,变成 RR 情况,按 3. 来后续处理
删除情况

case0:如果删除节点有两个孩子

  • 交换删除节点和后继节点的 key,value,递归删除后继节点,直到该节点没有孩子或只剩一个孩子

如果删除节点没有孩子或只剩一个孩子

case 1:删的是根节点

  • 删完了,直接将 root = null
  • 用剩余节点替换了根节点的 key,value,根节点孩子 = null,颜色保持黑色⚫️不变

删黑色会失衡,删红色不会失衡,但删黑色有一种简单情况

case 2:删的是黑⚫️,剩下的是红🔴,剩下这个红节点变黑⚫️

删除节点和剩下节点都是黑⚫️,触发双黑,双黑意思是,少了一个黑

case 3:被调整节点的兄弟为红🔴,此时两个侄子定为黑 ⚫️

  • 删除节点是左孩子,父亲左旋
  • 删除节点是右孩子,父亲右旋
  • 父亲和兄弟要变色,保证旋转后颜色平衡
  • 旋转的目的是让黑侄子变为删除节点的黑兄弟,对删除节点再次递归,进入 case 4 或 case 5

case 4:被调整节点的兄弟为黑⚫️,两个侄子都为黑 ⚫️

  • 将兄弟变红🔴,目的是将删除节点和兄弟那边的黑色高度同时减少 1
  • 如果父亲是红🔴,则需将父亲变为黑,避免红红,此时路径黑节点数目不变
  • 如果父亲是黑⚫️,说明这条路径还是少黑,再次让父节点触发双黑

case 5:被调整节点的兄弟为黑⚫️,至少一个红🔴侄子

  • 如果兄弟是左孩子,左侄子是红🔴,LL 不平衡
    • 将来删除节点这边少个黑,所以最后旋转过来的父亲需要变成黑⚫️,平衡起见,左侄子也是黑⚫️
    • 原来兄弟要成为父亲,需要保留父亲颜色
  • 如果兄弟是左孩子,右侄子是红🔴,LR 不平衡
    • 将来删除节点这边少个黑,所以最后旋转过来的父亲需要变成黑⚫️
    • 右侄子会取代原来父亲,因此它保留父亲颜色
    • 兄弟已经是黑了⚫️,无需改变
  • 如果兄弟是右孩子,右侄子是红🔴,RR 不平衡
    • 将来删除节点这边少个黑,所以最后旋转过来的父亲需要变成黑⚫️,平衡起见,右侄子也是黑⚫️
    • 原来兄弟要成为父亲,需要保留父亲颜色
  • 如果兄弟是右孩子,左侄子是红🔴,RL 不平衡
    • 将来删除节点这边少个黑,所以最后旋转过来的父亲需要变成黑⚫️
    • 左侄子会取代原来父亲,因此它保留父亲颜色
    • 兄弟已经是黑了⚫️,无需改变
完整代码
package com.itheima.datastructure.redblacktree;import static com.itheima.datastructure.redblacktree.RedBlackTree.Color.BLACK;
import static com.itheima.datastructure.redblacktree.RedBlackTree.Color.RED;/**
* <h3>红黑树</h3>
*/
public class RedBlackTree {enum Color {RED, BLACK;}Node root;static class Node {int key;Object value;Node left;Node right;Node parent;        // 父节点Color color = RED;  // 颜色public Node(int key, Object value) {this.key = key;this.value = value;}public Node(int key) {this.key = key;}public Node(int key, Color color) {this.key = key;this.color = color;}public Node(int key, Color color, Node left, Node right) {this.key = key;this.color = color;this.left = left;this.right = right;if (left != null) {left.parent = this;}if (right != null) {right.parent = this;}}// 是否是左孩子boolean isLeftChild() {return parent != null && parent.left == this;}// 叔叔Node uncle() {if (parent == null || parent.parent == null) {return null;}if (parent.isLeftChild()) {return parent.parent.right;} else {return parent.parent.left;}}// 兄弟Node sibling() {if (parent == null) {return null;}if (this.isLeftChild()) {return parent.right;} else {return parent.left;}}}// 判断红boolean isRed(Node node) {return node != null && node.color == RED;}// 判断黑boolean isBlack(Node node) {
//        return !isRed(node);return node == null || node.color == BLACK;}// 右旋 1. parent 的处理 2. 旋转后新根的父子关系private void rightRotate(Node pink) {Node parent = pink.parent;Node yellow = pink.left;Node green = yellow.right;if (green != null) {green.parent = pink;}yellow.right = pink;yellow.parent = parent;pink.left = green;pink.parent = yellow;if (parent == null) {root = yellow;} else if (parent.left == pink) {parent.left = yellow;} else {parent.right = yellow;}}// 左旋private void leftRotate(Node pink) {Node parent = pink.parent;Node yellow = pink.right;Node green = yellow.left;if (green != null) {green.parent = pink;}yellow.left = pink;yellow.parent = parent;pink.right = green;pink.parent = yellow;if (parent == null) {root = yellow;} else if (parent.left == pink) {parent.left = yellow;} else {parent.right = yellow;}}/*** 新增或更新* <br>* 正常增、遇到红红不平衡进行调整** @param key   键* @param value 值*/public void put(int key, Object value) {Node p = root;Node parent = null;while (p != null) {parent = p;if (key < p.key) {p = p.left;} else if (p.key < key) {p = p.right;} else {p.value = value; // 更新return;}}Node inserted = new Node(key, value);if (parent == null) {root = inserted;} else if (key < parent.key) {parent.left = inserted;inserted.parent = parent;} else {parent.right = inserted;inserted.parent = parent;}fixRedRed(inserted);}void fixRedRed(Node x) {// case 1 插入节点是根节点,变黑即可if (x == root) {x.color = BLACK;return;}// case 2 插入节点父亲是黑色,无需调整if (isBlack(x.parent)) {return;}/* case 3 当红红相邻,叔叔为红时需要将父亲、叔叔变黑、祖父变红,然后对祖父做递归处理*/Node parent = x.parent;Node uncle = x.uncle();Node grandparent = parent.parent;if (isRed(uncle)) {parent.color = BLACK;uncle.color = BLACK;grandparent.color = RED;fixRedRed(grandparent);return;}// case 4 当红红相邻,叔叔为黑时if (parent.isLeftChild() && x.isLeftChild()) { // LLparent.color = BLACK;grandparent.color = RED;rightRotate(grandparent);} else if (parent.isLeftChild()) { // LRleftRotate(parent);x.color = BLACK;grandparent.color = RED;rightRotate(grandparent);} else if (!x.isLeftChild()) { // RRparent.color = BLACK;grandparent.color = RED;leftRotate(grandparent);} else { // RLrightRotate(parent);x.color = BLACK;grandparent.color = RED;leftRotate(grandparent);}}/*** 删除* <br>* 正常删、会用到李代桃僵技巧、遇到黑黑不平衡进行调整** @param key 键*/public void remove(int key) {Node deleted = find(key);if (deleted == null) {return;}doRemove(deleted);}public boolean contains(int key) {return find(key) != null;}// 查找删除节点private Node find(int key) {Node p = root;while (p != null) {if (key < p.key) {p = p.left;} else if (p.key < key) {p = p.right;} else {return p;}}return null;}// 查找剩余节点private Node findReplaced(Node deleted) {if (deleted.left == null && deleted.right == null) {return null;}if (deleted.left == null) {return deleted.right;}if (deleted.right == null) {return deleted.left;}Node s = deleted.right;while (s.left != null) {s = s.left;}return s;}// 处理双黑 (case3、case4、case5)private void fixDoubleBlack(Node x) {if (x == root) {return;}Node parent = x.parent;Node sibling = x.sibling();// case 3 兄弟节点是红色if (isRed(sibling)) {if (x.isLeftChild()) {leftRotate(parent);} else {rightRotate(parent);}parent.color = RED;sibling.color = BLACK;fixDoubleBlack(x);return;}if (sibling != null) {// case 4 兄弟是黑色, 两个侄子也是黑色if (isBlack(sibling.left) && isBlack(sibling.right)) {sibling.color = RED;if (isRed(parent)) {parent.color = BLACK;} else {fixDoubleBlack(parent);}}// case 5 兄弟是黑色, 侄子有红色else {// LLif (sibling.isLeftChild() && isRed(sibling.left)) {rightRotate(parent);sibling.left.color = BLACK;sibling.color = parent.color;}// LRelse if (sibling.isLeftChild() && isRed(sibling.right)) {sibling.right.color = parent.color;leftRotate(sibling);rightRotate(parent);}// RLelse if (!sibling.isLeftChild() && isRed(sibling.left)) {sibling.left.color = parent.color;rightRotate(sibling);leftRotate(parent);}// RRelse {leftRotate(parent);sibling.right.color = BLACK;sibling.color = parent.color;}parent.color = BLACK;}} else {// @TODO 实际也不会出现,触发双黑后,兄弟节点不会为 nullfixDoubleBlack(parent);}}private void doRemove(Node deleted) {Node replaced = findReplaced(deleted);Node parent = deleted.parent;// 没有孩子if (replaced == null) {// case 1 删除的是根节点if (deleted == root) {root = null;} else {if (isBlack(deleted)) {// 双黑调整fixDoubleBlack(deleted);} else {// 红色叶子, 无需任何处理}if (deleted.isLeftChild()) {parent.left = null;} else {parent.right = null;}deleted.parent = null;}return;}// 有一个孩子if (deleted.left == null || deleted.right == null) {// case 1 删除的是根节点if (deleted == root) {root.key = replaced.key;root.value = replaced.value;root.left = root.right = null;} else {if (deleted.isLeftChild()) {parent.left = replaced;} else {parent.right = replaced;}replaced.parent = parent;deleted.left = deleted.right = deleted.parent = null;if (isBlack(deleted) && isBlack(replaced)) {// @TODO 实际不会有这种情况 因为只有一个孩子时 被删除节点是黑色 那么剩余节点只能是红色不会触发双黑fixDoubleBlack(replaced);} else {// case 2 删除是黑,剩下是红replaced.color = BLACK;}}return;}// case 0 有两个孩子 => 有一个孩子 或 没有孩子int t = deleted.key;deleted.key = replaced.key;replaced.key = t;Object v = deleted.value;deleted.value = replaced.value;replaced.value = v;doRemove(replaced);}
}
  • 以上代码中的 TODO 未作改正
小结
维度普通二叉搜索树AVL树红黑树
查询平均O(logn),最坏O(n)O(logn)O(logn)
插入平均O(logn),最坏O(n)O(logn)O(logn)
删除平均O(logn),最坏O(n)O(logn)O(logn)
平衡性不平衡严格平衡近似平衡
结构二叉树自平衡的二叉树具有红黑性质的自平衡二叉树
查找效率
插入删除效率中等

普通二叉搜索树插入、删除、查询的时间复杂度与树的高度相关,因此在最坏情况下,时间复杂度为O(n),而且容易退化成链表,查找效率低。

AVL树是一种高度平衡的二叉搜索树,其左右子树的高度差不超过1。因此,它能够在logn的平均时间内完成插入、删除、查询操作,但是在维护平衡的过程中,需要频繁地进行旋转操作,导致插入删除效率较低。

红黑树是一种近似平衡的二叉搜索树,它在保持高度平衡的同时,又能够保持较高的插入删除效率。红黑树通过节点着色和旋转操作来维护平衡。红黑树在维护平衡的过程中,能够进行较少的节点旋转操作,因此插入删除效率较高,并且查询效率也较高。

综上所述,红黑树具有较高的综合性能,是一种广泛应用的数据结构。

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

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

相关文章

关于js [GDOUCTF 2023]hate eat snake

查看页面源代码 发现snake.js文件 打开js文件 第7行定义了游戏的速度this.speed this.oldSpeed speed || 10 ; 全文搜索speed&#xff0c;在第237行发现自增代码this.speed; 注释或者删除自增代码 回到游戏页面 重玩游戏&#xff0c;等待60s即可 得到flag

Swift Combine 使用 handleEvents 操作符调试管道 从入门到精通二十五

Combine 系列 Swift Combine 从入门到精通一Swift Combine 发布者订阅者操作者 从入门到精通二Swift Combine 管道 从入门到精通三Swift Combine 发布者publisher的生命周期 从入门到精通四Swift Combine 操作符operations和Subjects发布者的生命周期 从入门到精通五Swift Com…

【微服务】mybatis typehandler使用详解

目录 一、前言 二、TypeHandler简介 2.1 什么是TypeHandler 2.1.1 TypeHandler特点 2.2 TypeHandler原理 2.3 mybatis自带的TypeHandler 三、环境准备 3.1 准备一张数据表 3.2 搭建一个springboot工程 3.2.1 基础依赖如下 3.2.2 核心配置文件 3.2.3 测试接口 四、T…

d3dcompiler_47.dll是什么,电脑出现d3dcompiler_47.dll丢失如何解决

当打开软件时提示“d3dcompiler_47.dll丢失”时&#xff0c;用户通常会看到类似于以下的错误消息&#xff1a; “无法启动此程序&#xff0c;因为计算机中丢失了d3dcompiler_47.dll。尝试重新安装该程序以解决此问题。” “找不到d3dcompiler_47.dll文件&#xff0c;因此应用…

破译一致性难题:Raft日志复制技术及成员变更问题详解

一、日志复制 Raft 算法是一种用于实现分布式系统中一致性状态机复制的共识算法。在 Raft 中&#xff0c;日志复制是保证集群数据一致性的关键机制。每个节点&#xff08;服务器&#xff09;都维护着一个日志&#xff0c;其中包含一系列的日志条目&#xff08;Log Entry&#x…

在 where子句中使用子查询(二)

目录 ANY ANY &#xff1a;功能上与 IN 是没有任何区别的 >ANY &#xff1a;比子查询返回的最小值要大 ALL >AL &#xff1a;比子查询返回的最大值要大 EXISTS() 判断 NOT EXISTS Oracle从入门到总裁:https://blog.csdn.net/weixin_67859959/article/details/135209…

Open3D 点云法向量计算与可视化 (25)

Open3D 点云法向量计算与可视化 (25) 一、算法原理二、算法实现三、可视化显示和长度调节一、算法原理 通常计算点云的法向量可以使用以下两种常见的方法: 最小二乘法(Least Squares Method):该方法通过拟合局部表面的平面来计算法向量。对于给定点周围的邻域,可以通过…

Peter算法小课堂—动态规划

Peter来啦&#xff0c;好久没有更新了呢 今天&#xff0c;我们来讨论讨论提高组的动态规划。 动态规划 动态规划有好多经典的题&#xff0c;有什么背包问题、正整数拆分、杨辉三角……但是&#xff0c;如果考到陌生的题&#xff0c;怎么办呢&#xff1f;比如说2000年提高组的…

apache 模式、优化、功能 与 nginx优化、应用

一、I/O模型——Input/Output模型 1.同步/异步 A程序需要调用B程序的某一个功能&#xff0c;A发送一个请求需要B完成一个任务 同步&#xff1a;B不会主动去通知A是否完成需要A自己去问 异步&#xff1a;B会主动通知A是否完成 2.阻塞/非阻塞 A发送一个请求需要B完成一个任务 …

勇宝趣学JavaScript ES6第三章(字符串的拓展)

已经写到系列教程的第三章了&#xff0c;本章节我们一起来探讨字符串的那些事。在我们的日常工作中&#xff0c;经常会用到模板字符串&#xff0c;还有一些字符串的方法&#xff0c;我们今天就来好好的品一品。 谢谢大家的点赞和收藏。 文章目录 一、字符串的方法1.1 charAt和c…

消息队列-RabbitMQ:延迟队列、rabbitmq 插件方式实现延迟队列、整合SpringBoot

十六、延迟队列 1、延迟队列概念 延时队列内部是有序的&#xff0c;最重要的特性就体现在它的延时属性上&#xff0c;延时队列中的元素是希望在指定时间到了以后或之前取出和处理&#xff0c;简单来说&#xff0c;延时队列就是用来存放需要在指定时间被处理的元素的队列。 延…

软考45-上午题-【数据库】-数据操纵语言DML

一、INSERT插入语句 向SQL的基本表中插入数据有两种方式&#xff1a; ①直接插入元组值 ②插入一个查询的结果值 1-1、直接插入元组值 【注意】&#xff1a; 列名序列是可选的&#xff0c;若是所有列都要插入数值&#xff0c;则可以不写列名序列。 示例&#xff1a; 1-2、插…

暑期宅家?计算机专业必看的8部电影!一定要安利给你们!

代码编程看上去枯燥乏味&#xff0c;但也是艺术的&#xff0c;感性的&#xff0c;计算机编程的许多概念被应用于电影中&#xff0c;其中有些非常之酷炫&#xff0c;它们甚至能帮助开发人员理解一些编程概念。 所以今天学姐来给大家推荐几部心中top级的编程人必看电影&#xff0…

matlab倒立摆小车LQR控制动画

1、内容简介 略 54-可以交流、咨询、答疑 2、内容说明 略 摆杆长度为 L&#xff0c;质量为 m 的单级倒立摆(摆杆的质心在杆的中心处)&#xff0c;小车的质量为 M。在水平方向施加控制力 u&#xff0c;相对参考系产生位移为 y。为了简化问题并且保其实质不变&#xff0c;忽…

数据结构:链表的冒泡排序

法一&#xff1a;修改指针指向 //法二 void maopao_link(link_p H){if(HNULL){printf("头节点为空\n");return;}if(link_empty(H)){printf("链表为空\n");return;}link_p tailNULL;while(H->next->next!tail){link_p pH;link_p qH->next;while(q…

抖音视频提取软件使用功能|抖音视频下载工具

我们的抖音视频提取软件是一款功能强大、易于操作的工具&#xff0c;旨在解决用户在获取抖音视频时需要逐个复制链接、下载的繁琐问题。我们的软件支持通过关键词搜索和分享链接两种方式获取抖音视频&#xff0c;方便用户快速找到自己感兴趣的内容。 主要功能模块&#xff1a;…

进程线程信号通道

4> 使用消息队列完成两个进程间相互通信 usr1代码&#xff1a; #include <myhead.h> //定义一个消息类型 struct msgbuf {long mtype;//消息类型char mtext[1024];//消息正文 }; #define MSGSIZE sizeof(struct msgbuf)-sizeof(long) int main(int argc, const char …

物奇ENC算法开关接口修改方法

物奇ENC算法开关接口修改 是否需要申请加入数字音频系统研究开发交流答疑群(课题组)&#xff1f;可加我微信hezkz17, 本群提供音频技术答疑服务&#xff0c;群赠送语音信号处理降噪算法&#xff0c;蓝牙耳机音频&#xff0c;DSP音频项目核心开发资料, 1 配置工具事件接口 2 代…

K线实战分析系列之十一:行情力量不足——平头形态

K线实战分析系列之十一&#xff1a;行情力量不足——平头形态 一、平头形态二、不同形态与平头形态的叠加三、总结平头形态 一、平头形态 前一根K线具有较长的实体&#xff0c;后一根K线的实体比较小&#xff0c;无论是多头还是空头的力量到第二根K线都被瓦解了多头上攻&#…

初识51单片机

##江科大51单片机学习 什么是单片机&#xff1f;&#xff1f;&#xff1f; 单片机&#xff0c;英文名&#xff0c;Micro Controller Unit&#xff0c;简称MCU&#xff08;tips&#xff1a;有人会简称它为CPU&#xff0c;但不是如此&#xff0c;CPU其实被集成在MCU中&#xff…