【高阶数据结构】红黑树

文章目录

  • 前言
  • 什么是红黑树
  • 红黑树的性质
  • 红黑树结点的定义
  • 红黑树的插入
    • 情况一
    • 情况二
    • 情况三
    • 插入代码总结
  • 验证是否为红黑树
  • 红黑树的删除

前言

前面我们学习了 AVL 树——高度平衡的二叉搜索树,AVL 树保证了结点的左右子树的高度差的绝对值不超过 1,也就是结点的左右子树的高度是绝对平衡的,虽然这种结构的查询速度非常的快,但是因为它要保证左右子树的绝对平衡,所以对 AVL 树进行增加或者删除操作的时候,就需要进行多次旋转,而对树进行旋转也是需要时间的,所以 AVL 树只适合存储一些静态的不经常变化的数据。那么要想保证查询速度,也要对数据进行增加和删除操作的话,就需要使用另一个数据结构——红黑树。

什么是红黑树

红黑树是一种二叉搜索树,但在每个节点上增加了一个存储位表示节点的颜色,可以是 Red 或者 Black。通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出两倍,因而是接近平衡的。

在这里插入图片描述

红黑树的性质

为了保证红黑树的查找速率以及增加和删除的速度,红黑树具有以下性质:

  1. 从根节点到叶子节点的路径中,最长路径最多是最短路径的二倍
  2. 每个节点不是红色就是黑色
  3. 根节点是黑色的
  4. 如果一个节点是红色的,则如果它的两个孩子节点是黑色的(一条路径上不存在两个连续的红色节点)
  5. 对于每个节点,从该结点到其所有后代节点的简单路径上,均包含相同数目的黑色节点(黑色节点的数量包括 NULL 节点)
  6. 每个叶子节点都是黑色的(此处的叶子结点指的是空结点)

这里对一些性质进行演示:

为什么从根节点到叶子节点的路径中,最长路径最多是最短路径的二倍?

这个性质是通过性质4、5得来的:
在这里插入图片描述

红黑树结点的定义

首先通过一个枚举类来表示颜色:

public enum COLOR {RED, BLACK;
}

红黑树节点的定义:

class RBTreeNode {public RBTreeNode left;public RBTreeNode right;public RBTreeNode parent;public rbtree.COLOR color = rbtree.COLOR.RED; //结点的颜色public int val;public RBTreeNode(int val) {this.val = val;this.color = COLOR.RED;}
}

在这里我们将节点的颜色默认设置为了红色,这是为什么呢?

因为红黑树的性质:从任一节点到其子树中每个叶子节点的路径都包含相同数量的黑色节点。将节点默认设置为红色,插入时不会违反红黑树的性质,因为红色的节点不会影响路径上黑色节点的数量。而如果默认颜色设置为黑色,每次插入新节点都可能违反红黑树的性质,需要频繁调整树的结构,导致效率降低。因此,将红黑树的结点默认颜色设置为红色是为了保持树的平衡,提高插入操作的效率。

红黑树的插入

因为红黑树也是属于特殊的二叉搜索树,所以在插入数据的时候,还是按照二叉搜索树插入的做法一样,当数据插入之后,我们需要做的就是检测新节点插入之后,红黑树的性质是否被破坏。

这里的破坏性质通常是指:因为新插入的节点的颜色默认是红色,如果新插入的节点的双亲节点的颜色是黑色的话,就没有破坏红黑树的性质,不需要做出修改;而如果插入的节点的双亲结点也是红色的话,就不符合红黑树的性质——红色结点的左右孩子节点的颜色都是黑色(在一条路径中不存在两个连续的红色节点),此时就需要对红黑树的结构进行修改。

这里我们将插入节点后需要调整红黑树结构的情况给列举出来:

这里我们约定:cur 节点为当前节点,p为父节点,g为祖父节点,u为叔叔节点

情况一

当 cur 为红色,p 为红色,g 为黑色,u 存在且为红色:

在这里插入图片描述

这里 cur 是新插入的节点,插入之后 p 为红色,cur 为红色,一条路径上存在两个连续的红色节点,此时就需要对红黑树的结构进行调整。这种情况还是比较容易解决的:这种情况,我们需要将 p 和 u 都改为黑色,并且为了保证路径上黑色节点的数量不变,还需要将 g 节点的颜色改为 RED,这样就没有破坏红黑树的性质。

在这里插入图片描述

修改 p、u 和 g 的颜色之后,还没有结束,因为红黑树的性质中还有一条性质就是:根节点的颜色必须为黑色,所以我们在进行上面的操作了之后,不管根节点为啥颜色,都需要进行 root.color = COLOR.BLACK 的操作。

通过代码体现就是这样:

while (parent != null && parent.color == COLOR.RED) {RBTreeNode grandfather = parent.parent; //grandfather不可能为null,因为如果parent为红色,那么就一定存在父亲节点,因为红黑树的根节点是黑色RBTreeNode uncle = null;if (grandfather.left == parent) {uncle = grandfather.right;}else {uncle = grandfather.left;}if (uncle != null && uncle.color == COLOR.RED) {//情况一parent.color = COLOR.BLACK;uncle.color = COLOR.BLACK;grandfather.color = COLOR.RED;//当grandfather的节点颜色变为了红色之后,可能又会破坏其他树的结构,所以需要继续向上调整cur = grandfather;parent = cur.parent;}
}
root.color = COLOR.BLACK; //将根节点的颜色修改为黑色

情况二

当 cur 为红色,p 为红色,g 为黑色,u 不存在或者 u 存在且为黑色:

这种情况往往不是刚插入时候造成的,而是因为在调整的过程中出现的:

在这里插入图片描述

所以这种情况只可能在向上调整的过程中才会出现:

在这里插入图片描述

对于这种情况的解决方式就是对 g 的左右子树进行右旋操作之后,将 p 的颜色改为黑色,g 的颜色改为红色:

在这里插入图片描述

这是 u 不存在的情况:

在这里插入图片描述

同样的,这里是右旋,将上面的情况进行镜像处理,就需要进行左旋操作了:

在这里插入图片描述

所以当 cur 为红色,p 为红色,g 为黑色。u不存在或者 u 存在且颜色为黑色的做法就可以总结为:

  1. 当 p 为 g 的左孩子,cur 为 p 的左孩子的时候:
  • (1)将 g 节点的左右子树进行右旋操作
  • (2)将 g 节点的颜色修改为红色,p 节点的颜色修改为黑色
  1. 当 p 为 g 的右孩子,cur 为 p 的右孩子的时候:
  • (1)将 g 节点的左右子树进行左旋操作
  • (2) 将 g 节点的颜色修改为红色,p 节点的颜色修改为黑色

通过代码体现就是这样:

while (parent != null && parent.color == COLOR.RED) {RBTreeNode grandfather = parent.parent; //grandfather不可能为null,因为如果parent为红色,那么就一定存在父亲节点,因为红黑树的根节点是黑色RBTreeNode uncle = null;if (grandfather.left == parent) {uncle = grandfather.right;if (uncle != null && uncle.color == COLOR.RED) {//情况一parent.color = COLOR.BLACK;uncle.color = COLOR.BLACK;grandfather.color = COLOR.RED;//当grandfather的节点颜色变为了红色之后,可能又会破坏其他树的结构,所以需要继续向上调整cur = grandfather;parent = cur.parent;}else {//else 就表示uncle为空或者uncle不为空且uncle颜色为黑色的情况if (cur == parent.left) {rotateRight(grandfather);grandfather.color = COLOR.RED;parent.color = COLOR.BLACK;}}}else {uncle = grandfather.left;if (uncle != null && uncle.color == COLOR.RED) {//情况一parent.color = COLOR.BLACK;uncle.color = COLOR.BLACK;grandfather.color = COLOR.RED;//当grandfather的节点颜色变为了红色之后,可能又会破坏其他树的结构,所以需要继续向上调整cur = grandfather;parent = cur.parent;}else {//else 就表示uncle为空或者uncle不为空且uncle颜色为黑色的情况if (cur == parent.right) {rotateLeft(grandfather);grandfather.color = COLOR.RED;parent.color = COLOR.BLACK;}}}
}
root.color = COLOR.BLACK;
return true;

右旋操作:

public void rotateRight(RBTreeNode parent) {RBTreeNode subL = parent.left;RBTreeNode subLR = subL.right;parent.left = subLR;if (subLR != null) {subLR.parent = parent;}RBTreeNode pParent = parent.parent;if (root == parent) {root = subL;subL.parent = null;}else {if (pParent.left == parent) {pParent.left = subLR;}else {pParent.right = subLR;}subLR.parent = pParent;}subL.right = parent;parent.parent = subL;
}

左旋操作:

public void rotateLeft(RBTreeNode parent) {RBTreeNode subR = parent.right;RBTreeNode subRL = subR.left;RBTreeNode pParent = parent.parent;parent.right = subRL;if (subRL != null) {subRL.parent = parent;}if (root == parent) {root = subR;root.parent = pParent;}else {if (pParent.left == parent) {pParent.left = subR;else {pParent.right = subR;}subR.parent = pParent;}subR.left = parent;parent.parent = subR;
}

情况三

第三种情况还是 cur 为红色,p 为红色,g 为黑色,u 不存在或者 u 存在且为黑色,但是呢?这种情况不是和第二种情况一样,p 位于 g 的左侧,cur 位于 p 的左侧、p 位于 g 的右侧,cur 位于 p 的右侧这种相同方向的情况,而是 p 位于 g 的左侧,cur 位于 p 的右侧、p 位于 g 的右侧,cur 位于 g 的左侧:

在这里插入图片描述
同样这种情况也是在调整的过程中才会出现的:

在这里插入图片描述

当出现这种情况的时候,通过右旋 g 节点的左右子树,然后修改 p 和 g 的颜色是无法解决的:

在这里插入图片描述

当出现这种情况的时候,需要先对 p 节点的左右子树进行左旋操作:

在这里插入图片描述

对 p 的左右子树进行左旋操作之后,就变成了第二类情况,接下来对 g 的左右子树进行右旋操作之后,将 g 节点的颜色修改为红色、p 节点的颜色修改为黑色就可以达到目的了。

这是当 p 为 g 的左子树,cur 为 p 的右子树的情况,对于 p 为 g 的右子树,cur 为 p 的左子树的解决方法是类似的:

在这里插入图片描述

通过代码展示就是这样的:

当 p 为 g 的左子树,cur 为 p 的右子树:

rotateLeft(parent);
RBTreeNode tmp = parent;
parent = cur;
cur = tmp;
rotateRight(grandfather);
grandfather.color = COLOR.RED;
parent.color = COLOR.BLACK;

当 p 为 g 的右子树,cur 为 p 的左子树:

rotateRight(parent);
RBTreeNode tmp = parent;
parent = cur;
cur = tmp;
rotateLeft(grandfather);
grandfather.color = COLOR.RED;
parent.color = COLOR.BLACK;

插入代码总结

public class RBTree {class RBTreeNode {public RBTreeNode left;public RBTreeNode right;public RBTreeNode parent;public rbtree.COLOR color; //结点的颜色public int val;public RBTreeNode(int val) {this.val = val;this.color = COLOR.RED;}}private RBTreeNode root;public boolean insert(int data) {RBTreeNode node = new RBTreeNode(data);if (root == null) {root = node;node.color = COLOR.BLACK;return true;}RBTreeNode cur = root, parent = null; //parent节点记录cur节点的双亲节点while (cur != null) {if (cur.val < data) {parent = cur;cur = cur.right;}else if (cur.val > data) {parent = cur;cur = cur.left;}else {return false; //二叉搜索树中不存在重复的元素}}if (data > parent.val) {parent.right = node;}else {parent.left = node;}node.parent = parent;cur = node;while (parent != null && parent.color == COLOR.RED) {RBTreeNode grandfather = parent.parent; //grandfather不可能为null,因为如果parent为红色,那么就一定存在父亲节点,因为红黑树的根节点是黑色RBTreeNode uncle = null;if (grandfather.left == parent) {uncle = grandfather.right;if (uncle != null && uncle.color == COLOR.RED) {//情况一parent.color = COLOR.BLACK;uncle.color = COLOR.BLACK;grandfather.color = COLOR.RED;//当grandfather的节点颜色变为了红色之后,可能又会破坏其他树的结构,所以需要继续向上调整cur = grandfather;parent = cur.parent;}else {//else 就表示uncle为空或者uncle不为空且uncle颜色为黑色的情况if (cur == parent.right) {rotateLeft(parent);RBTreeNode tmp = parent;parent = cur;cur = tmp;}rotateRight(grandfather);grandfather.color = COLOR.RED;parent.color = COLOR.BLACK;}}else {uncle = grandfather.left;if (uncle != null && uncle.color == COLOR.RED) {//情况一parent.color = COLOR.BLACK;uncle.color = COLOR.BLACK;grandfather.color = COLOR.RED;//当grandfather的节点颜色变为了红色之后,可能又会破坏其他树的结构,所以需要继续向上调整cur = grandfather;parent = cur.parent;}else {//else 就表示uncle为空或者uncle不为空且uncle颜色为黑色的情况if (cur == parent.left) {rotateRight(parent);RBTreeNode tmp = parent;parent = cur;cur = tmp;}rotateLeft(grandfather);grandfather.color = COLOR.RED;parent.color = COLOR.BLACK;}}}root.color = COLOR.BLACK;return true;}public void rotateRight(RBTreeNode parent) {RBTreeNode subL = parent.left;RBTreeNode subLR = subL.right;parent.left = subLR;if (subLR != null) {subLR.parent = parent;}RBTreeNode pParent = parent.parent;if (root == parent) {root = subL;subL.parent = null;}else {if (pParent.left == parent) {pParent.left = subLR;}else {pParent.right = subLR;}subLR.parent = pParent;}subL.right = parent;parent.parent = subL;}public void rotateLeft(RBTreeNode parent) {RBTreeNode subR = parent.right;RBTreeNode subRL = subR.left;RBTreeNode pParent = parent.parent;parent.right = subRL;if (subRL != null) {subRL.parent = parent;}if (root == parent) {root = subR;root.parent = pParent;}else {if (pParent.left == parent) {pParent.left = subR;}else {pParent.right = subR;}subR.parent = pParent;}subR.left = parent;parent.parent = subR;}
}

验证是否为红黑树

验证是否为红黑树,首先需要验证是否为二叉搜索树,然后验证每个路径上的黑色节点的数量是否相等,还需要验证在一条路径中是否存在两个连续的红色节点:

检验是否为二叉搜索树:

private int prev = Integer.MIN_VALUE;public boolean isBinarySearchTree(RBTreeNode root) {if (root == null) return true;boolean l = isBinarySearchTree(root.left);if (!l) return false;if (prev < root.val) {prev = root.val;return isBinarySearchTree(root.right);}else return false;
}

校验所有路径中的黑色节点的数量是否相等:

//先计算出红黑树中其中一条路径中黑色节点的数量
public int blackNum(RBTreeNode root) {if (root == null) return 0;int count = 0;RBTreeNode cur = root;if (root.left != null) {while (cur != null) {if (cur.color == COLOR.BLACK) count++;cur = cur.left;}}else if (root.right != null){while (cur != null) {if (cur.color == COLOR.BLACK) count++;cur = cur.right;}}else {count = root.color == COLOR.BLACK ? 1 : 0;}return count;
}//根据传入的路径中黑色节点的数量判断是否所有路径上的黑色节点的数量相同
public boolean checkBlackNum(RBTreeNode root, int pathBlackNum, int blackNum) {if (root == null) return true;if (root.color == COLOR.BLACK) pathBlackNum++;if (root.left == null && root.right == null) {if (pathBlackNum == blackNum) return true;else return false;}return checkBlackNum(root.left, pathBlackNum, blackNum)&& checkBlackNum(root.right, pathBlackNum, blackNum);
}

判断一条路径上是否存在两个连续的红色节点:

public boolean checkRedColor(RBTreeNode root) {if (root == null) return true;if (root.color == COLOR.RED) {if (root.left != null && root.left.color == COLOR.RED) return false;if (root.right != null && root.right.color == COLOR.RED) return false;}return checkRedColor(root.left) && checkRedColor(root.right);
}

整理接口:

public boolean checkRBTree(RBTreeNode root) {int blackNum = blackNum(root);return isBinarySearchTree(root) && checkBlackNum(root,0,blackNum)&& checkRedColor(root);
}

红黑树的删除

红黑树的删除操作主要涉及以下几个步骤:

  1. 定位节点:找到要删除的节点。如果要删除的节点有两个子节点,则需要找到该节点的后继节点(通常是右子树中的最小节点)来替代要删除的节点。
  2. 执行删除:执行标准的二叉搜索树(BST)的删除操作。这涉及到将后继节点的值复制到当前节点,并删除后继节点。如果后继节点有子节点,这些子节点将被转移到被删除节点的位置。
  3. 修复红黑树性质:删除节点后,可能会破坏红黑树的性质。因此,需要通过一系列的旋转和颜色更改操作来修复这些性质。这些操作包括左旋、右旋以及重新着色节点,以确保满足红黑树的五大特征。
  4. 处理特殊情况:在删除操作中,可能会遇到一些特殊情况,例如要删除的节点是黑色且拥有两个红色子节点,或者要删除的节点是根节点等。这些情况需要特殊的处理方式来确保红黑树的性质得到维护。

这里我就不为大家详细介绍了,大家下去可以自己了解了解。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

bash脚本学习笔记

一、扫盲 脚本文件是一种文本文件&#xff0c;其中包含了一系列的命令和指令&#xff0c;可以被操作系统解释器直接解释执行。脚本文件通常被用来完成特定的任务或执行重复性的操作。 脚本文件通常以某种编程语言的语法编写&#xff0c;例如 Bash、Python、Perl、Ruby 等等。…

png,jpg,bmp等格式图像转.npy文件(附代码)

目录 一、npy文件二、代码三、转后效果 一、npy文件 .npy 文件是 NumPy 库用于存储数组的二进制文件格式。这种文件可以存储一个或多个 NumPy 数组对象。.npy 文件在保存和加载 NumPy 数组时非常有用&#xff0c;因为它们可以用于在磁盘上高效地存储数据&#xff0c;并且在加载…

IEC104 S帧超时判定客户与服务端不匹配造成的异常链接问题分析

2、通过ss命令发现确有链接端口变化&#xff0c;与设备约一天一次的重连&#xff0c;通过抓包&#xff08;tcpdump -vvv -nn port 1001 -w 0926.cap&#xff09;分析得以下现象 2.1、异常情况时未对设备的I帧均匀的回S帧进行确认&#xff0c;正常情况时均匀的回S帧进行确认 2.…

QT播放gstreamer命令(三)---使用QMediaPlayer

前文&#xff1a; 因为之前听说过&#xff0c;QMediaPlayer已经集成了gstreamer&#xff0c;但是并没有什么接口来例子来说明&#xff0c;根本看不出来有任何gstreamer的形式&#xff0c;于是在QT5助手里面搜了一下&#xff0c;发现确实有gstreamer的痕迹&#xff0c;但是例子写…

【数据结构】链表的概念 及 分类 (使用比喻解释概念)

一. 链表的概念及结构 概念&#xff1a;链表是一种 物理存储结构上非连续 、非顺序的存储结构&#xff0c;数据元素的 逻辑顺序 是通过链表中的 指针链接 次序实现的。 1.1 超级通俗的比喻 链表的结构跟火车车厢相似&#xff0c;淡季时车次的车厢会相应减少&#xff0c;旺季时车…

关于bypassuac的探究——思考

我们所使用的几个api&#xff0c;如RegCreateKeyExA、RegSetKeyExA都是直接修改注册表的操作&#xff0c;这种操作应该被归类为敏感操作&#xff0c;那么这里会不会被杀软拦截呢&#xff0c;去测试一下 windows defender正常上线 获取到的权限也是bypassuac后的权限 再看一下…

【计算机视觉】目标检测 |滑动窗口算法、YOLO、RCNN系列算法

一、概述 首先通过前面对计算机视觉领域中的卷积神经网络进行了解和学习&#xff0c;我们知道&#xff0c;可以通过卷积神经网络对图像进行分类。 如果还想继续深入&#xff0c;会涉及到目标定位(object location)的问题。在图像分类的基础上(Image classification)的基础上…

笔记本加装内存条

文章目录 一、拆前准备1.分析短板2.内存条选购3.工具准备 二、拆机实操1.拆后盖2.去静电、电源断电3.斜插45度&#xff0c;听到“嗒”的一声&#xff0c;说明成功了4.插回电源&#xff0c;开机测试 一、拆前准备 1.分析短板 内存、外存、显卡、CPU&#xff0c;提高短板不亚于…

【测试运维】web自动化全知识点笔记第1篇:什么是Web自动化测试(已分享,附代码)

本系列文章md笔记&#xff08;已分享&#xff09;主要讨论Web自动化测试相关知识。了解什么是自动化&#xff0c;理解什么是自动化测试以及为什么要使用自动化测试。具体包含&#xff1a;WebDriver的基本操作&#xff0c;WebDriver的鼠标、键盘操作&#xff0c;下拉选择框、警告…

【网络协议分析】利用Wireshark分析IP分片

一、实验目的 利用Wireshark软件抓包分析IP分片&#xff0c;了解IP分片的工作原理。 二、实验过程 1、网络拓扑 设备 IP地址 设备接口 MTU AR1 172.30.132.164 Ethernet 0/0/0 700 AR2 172.30.132.165 Ethernet 0/0/0 1200 2、实验过程 &#xff08;1&#xf…

Juniper SRX防火墙HA Cluster更换故障设备

一、设备状态信息确认及备份 如果遇到整机挂掉&#xff0c;仅需要备份状态的设备&#xff0c;如果fpc状态异常RMA整机&#xff0c;最好是能做全部的备份&#xff0c;毕竟数据无价&#xff0c;多一分备份不会有坏处 登陆SRX node0和node1系统 1、配置进行备份 node0> conf…

类银河恶魔城学习记录1-1 Player状态机的搭建 P28

对状态机的介绍 什么是状态机&#xff1f;一篇文章就够了 - 知乎 说实话&#xff0c;目前并不能深入理解状态机的奇妙之处&#xff08;当然&#xff0c;我觉得状态机作为教程的重要组成部分是不得不理解的&#xff0c;所以以下我会对游戏教程内的状态机做一些我认为的解释&am…