【高阶数据结构】红黑树 {概念及性质;红黑树节点的定义;红黑树插入操作详细解释;红黑树的验证}

红黑树

一、红黑树的概念

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

在这里插入图片描述

AVL树 VS 红黑树

  • 红黑树是一种特化的AVL树,都是在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能。

  • AVL树要求每棵子树的左右高度差不超过1,是严格平衡;而红黑树要求最长路径不超过最短路径的2倍,是接近平衡。

  • 而红黑树是一种AVL树的变体,它要求最长路径不超过最短路径的2倍,左右子树高差有可能大于 1。所以红黑树不是严格意义上的平衡二叉树(AVL),但对之进行平衡的代价较低, 其平均统计性能要强于 AVL

  • 相对而言,插入或删除同样的数据,AVL树旋转的更多,而红黑树则旋转的更少效率相对较高


二、红黑树的性质

红黑树是每个结点都带有颜色属性的二叉查找树,颜色或红色或黑色。 在二叉查找树强制一般要求以外,对于任何有效的红黑树我们增加了如下的额外要求:

  • 性质1. 结点是红色或黑色。

  • 性质2. 根结点是黑色。

  • 性质3. 每个红色结点的两个子结点都是黑色。(每条路径上不能有两个连续的红色结点)

  • 性质4. 从任一结点到其每个叶子的所有路径都包含相同数目的黑色结点。 (每条路径上的黑色节点数量相同)

  • 性质5. 所有NIL结点都是黑色的。(NIL节点即空结点)

这些约束强制了红黑树的关键性质: 从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。结果是这个树大致上是平衡的。因为操作比如插入、删除和查找某个值的最坏情况时间都要求与树的高度成比例,这个在高度上的理论上限允许红黑树在最坏情况下都是高效的,而不同于普通的二叉查找树。

是性质3导致路径上不能有两个连续的红色结点确保了这个结果。最短的可能路径都是黑色结点,最长的可能路径有交替的红色和黑色结点。因为根据性质4所有路径都有相同数目的黑色结点,这就表明了没有路径能多于任何其他路径的两倍长。

思考:新插入的节点应该设为黑色还是红色?

  • 如果将新插入的节点设为黑色,不管插到那条路径都必然违反性质4。

  • 如果将新插入的节点设为红色:如果父节点是红色则违反性质3,需要进行调整;如果父节点是黑色就正常插入,无需调整。

  • 对比两种情况,最终选择将新插入的节点设为红色。


三、红黑树节点的定义

enum Color{RED,BLACK
};template <class K, class V>
struct RBTreeNode{RBTreeNode<K,V> *_left;RBTreeNode<K,V> *_right;RBTreeNode<K,V> *_parent;pair<K,V> _kv;Color _color; //颜色属性,红或黑RBTreeNode(const pair<K,V> &kv=pair<K,V>(), Color color = RED):_left(nullptr),_right(nullptr),_parent(nullptr),_kv(kv),_color(color){}
};

四、红黑树的插入操作

红黑树是在二叉搜索树的基础上加上其平衡限制条件,因此红黑树的插入可分为两步:

  1. 按照二叉搜索的树规则插入新节点

  2. 检测新节点插入后,红黑树的性质是否造到破坏。因为新节点的默认颜色是红色,因此:

    • 如果新插入的节点是根节点,需要将节点变为黑色以满足性质2。
    • 如果父节点是黑色的,没有违反红黑树的任何性质,则不需要调整;
    • 但如果父节点颜色为红色时,就违反了性质3:路径上不能有两个连续的红色结点。此时需要对红黑树分情况来讨论:

在讲解情况三、四、五之前,先说明一下:

  • cur为当前节点(关注节点),p(parent)为父节点,g(grandparent)为祖父节点,u(uncle)为叔叔节点;
  • cur不一定就是新插入的节点,也有可能是因为 cur 的子树在调整的过程中将 cur 节点的颜色由黑色改成红色。

4.1 情况一:u存在且为红

情况一: cur为红,p为红,g为黑,u存在且为红

抽象分析:

在这里插入图片描述

  1. 因为cur和p都为红色违反性质3,所以一定要把p变为黑色。
  2. 但只变p又违反性质4各路径上黑色节点的数量不同,所以要把u也变为黑色。
  3. 但原来所有路径上只有1个黑色节点(可见的)而现在变为2个。如果g树是子树,又会使整棵树违反性质4。所以要把g变为红色。
  4. g的父节点也可能是红色,所以要继续向上调整。

解决方式:变色并继续向上调整

  1. 将p,u都改为黑色,g改为红色;
  2. 如果g不为根,就把g当成cur继续向上调整;
  3. 如果g为根,就把g变为黑色。性质2:根节点是黑色的。

具体分析:

cur就是新插入的节点:

在这里插入图片描述

cur节点原来是黑色之后又被调整为红色:

在这里插入图片描述

注意:a,b,c,d,e可能是连续的几层黑色节点(要求每条路径的黑色节点数量相同),然后才出现上述情况。因为情况太多,过于复杂故作省略。


4.2 情况二:u不存在/u存在且为黑(单旋)

情况二: cur为红,p为红,g为黑,u不存在/u存在且为黑(单旋)

抽象分析:
在这里插入图片描述

  1. 因为cur和p都为红色违反性质3,所以一定要把p变为黑色。
  2. 但只变p使左路黑节点+1违反性质4,因此还要以g为轴点右单旋,使左路黑节点-1。
  3. 但此时由于右单旋使右路黑节点+1,所以要将g变为红色,右路黑节点-1。最终满足性质4。

解决方式:单旋+变色

  1. 如果p为g的左孩子,cur为p的左孩子(左左),则对g进行右单旋;
  2. 如果p为g的右孩子,cur为p的右孩子(右右),则对g进行左单旋;
  3. p、g变色–p变黑色,g变红色。
  4. 完成旋转变色后每条路径的黑节点数量相同且与插入前也相同,并且根节点为黑色不需要继续往上处理。

具体分析:u 的情况有两种

uncle节点不存在:

如果 u 节点不存在,则 cur 一定是新插入节点,因为如果 cur 不是新插入节点,则 cur 和 p 一定有一个节点的颜色是黑色,就不满足性质4:每条路径黑色节点个数相同。

在这里插入图片描述

uncle节点存在且为黑色:

如果 u 节点存在且为黑色,那么 cur 节点原来的颜色也一定是黑色的,现在看到其是红色的原因是因为 cur 的子树在调整的过程中将 cur 节点的颜色由黑色改成红色。

在这里插入图片描述

注意:a,b,c,d,e可能是连续的几层黑色节点(要求每条路径的黑色节点数量相同),然后才出现上述情况。因为情况太多,过于复杂故作省略。


4.3 情况三:u不存在/u存在且为黑(双旋)

情况三: cur为红,p为红,g为黑,u不存在/u存在且为黑(双旋)

抽象图:
在这里插入图片描述
情况三先以p为轴点左单旋,转换为情况二。

解决方式:双旋+变色

  1. p为g的左孩子,cur为p的右孩子(左右),则先对p做左单旋,再对g做右单旋;
  2. p为g的右孩子,cur为p的左孩子(右左),则先对p做右单旋,再对g做左单旋;
  3. cur、g变色–cur变黑色,g变红色。
  4. 完成旋转变色后每条路径的黑节点数量相同且与插入前也相同,并且根节点为黑色不需要继续往上处理。

具体分析:

uncle节点不存在

在这里插入图片描述

uncle节点存在且为黑色:

在这里插入图片描述

注意:a,b,c,d,e可能是连续的几层黑色节点(要求每条路径的黑色节点数量相同),然后才出现上述情况。因为情况太多,过于复杂故作省略。

总结:

  • 二叉树插入操作的难点在于通过变色和旋转操作恢复红黑树的性质,性质得到满足红黑树就能做到近似平衡:最长路径不超过最短路径的两倍。
  • 恢复的最终目的:1.关注子树满足红黑树的所有性质 2.插入前后关注子树每条路径的黑节点数量不变(保证整棵树的性质4)

4.4 实现代码

template <class K, class V>
bool RBTree<K,V>::Insert(const pair<K,V> &kv)
{//1.按照二叉搜索的树规则插入新节点if(_root == nullptr){_root = new Node(kv, BLACK); //性质2:根节点是黑色的return true;}Node *cur = _root;Node *parent = nullptr;while(cur != nullptr){if(kv.first > cur->_kv.first){parent = cur;cur = cur->_right;}else if(kv.first < cur->_kv.first){parent  = cur;cur = cur->_left;}else{return false;}}cur = new Node(kv,RED); //新插入的节点是红色的if(kv.first > parent->_kv.first){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;//2.检测新节点插入后,红黑树的性质是否造到破坏。//如果上一次循环中grandparent为根节点,此次循环parent == nullptr,结束调整。//如果其父节点的颜色是黑色,没有违反红黑树的任何性质,则不需要调整;while(parent != nullptr && parent->_color == RED) {Node *grandparent = parent->_parent;//增加断言,是为了方便找出一般错误。assert(grandparent != nullptr); //因为父节点是红色的,所以祖父节点一定不为空,性质2assert(grandparent->_color == BLACK); //因为父节点是红色的,所以祖父节点一定是黑色,性质3Node *uncle = grandparent->_left;if(parent == grandparent->_left)uncle = grandparent->_right;if(uncle != nullptr && uncle->_color == RED) //情况一:uncle存在且为红{//p,u变黑,g变红,继续向上调整。parent->_color = uncle->_color = BLACK;grandparent->_color = RED;cur = grandparent;parent = cur->_parent;}else //情况二、三:uncle不存在或uncle存在且为黑{//需要进行旋转变色处理,先要判断旋转方式。if(parent == grandparent->_left){if(cur == parent->_left) //左左{RotateR(grandparent);parent->_color = BLACK;grandparent->_color = RED;}else{ //左右RotateL(parent);RotateR(grandparent);cur->_color = BLACK;grandparent->_color = RED;}}else{if(cur == parent->_right) //右右{RotateL(grandparent);parent->_color = BLACK;grandparent->_color = RED;}else{ //右左RotateR(parent);RotateL(grandparent);cur->_color = BLACK;grandparent->_color = RED;}}break; //完成旋转变色后每条路径的黑节点数量相同且根节点为黑色不需要继续往上处理。} //end of else} //end of while//如果上一次循环中grandparent为根节点,循环结束后要将根节点再改为黑色,性质2。if(cur == _root)cur->_color = BLACK;return true;}

五、红黑树的验证

红黑树的检测分为两步:

  1. 检测其是否满足二叉搜索树(中序遍历是否为有序序列)
  2. 检测其是否满足红黑树的性质
bool IsValidRBTree(){//空树也是红黑树if(_root == nullptr) return true;//检查性质2:if(_root->_color != BLACK){cout << "违反性质2:根节点不为黑色!" << endl;return false;}//检查性质3,4:int benchmark = 0;return PrevCheck(_root, 0, benchmark);
}//blacknum:用于记录当前路径的黑色节点个数,不能传引用。
//benchmark:用于记录第一条路径的黑色节点个数。需要传引用,返回给上层递归。
bool _IsValidRBTree(Node *root, int blacknum, int &benchmark){if(root == nullptr){if(benchmark == 0) //表示第一条路径遍历完{benchmark = blacknum; //记录第一条路径的黑色节点个数return true;}else{if(blacknum != benchmark) //如果其他路径的blacknum与第一条路径不同,说明违反性质4{cout << "违反性质4:从任意节点到每个叶子节点的所有路径都包含相同数目的黑色节点!" << endl;return false;}else{return true;}}}//检查性质3:if(root->_color == RED && root->_parent->_color == RED){cout << "违反性质3:路径上有两个连续的红色节点!" << endl;return false;}if(root->_color == BLACK){++blacknum; }return PrevCheck(root->_left, blacknum, benchmark)&& PrevCheck(root->_right, blacknum, benchmark);
}

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

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

相关文章

剑指 Offer 62. 圆圈中最后剩下的数字(简单)

题目&#xff1a; class Solution { public:int lastRemaining(int n, int m) {int pos 0;for(int i2;i<n;i){pos (posm)%i;}return pos;} };作者&#xff1a;想吃火锅的木易 链接&#xff1a;详细题解 来源&#xff1a;力扣&#xff08;LeetCode&#xff09;

【校招VIP】前端校招考点之UDP

考点介绍&#xff1a; UDP是非面向连接协议&#xff0c;使用udp协议通讯并不需要建立连接&#xff0c;它只负责把数据尽可能发送出去&#xff0c;并不可靠&#xff0c;在接收端&#xff0c;UDP把每个消息断放入队列中&#xff0c;接收端程序从队列中读取数据。 『前端校招考点…

ThinkPHP 文件上传 fileSystem 扩展的使用

ThinkPHP 文件上传 ThinkPHP 文件上传 扩展 filesystem一、安装 FileSystem 扩展二、认识 filesystem 配置文件 config/filesystem.php三、上传验证&#xff08;涉及到验证器的知识点&#xff09;四、文件上传demo ThinkPHP 文件上传 扩展 filesystem ThinkPHP 为我们 提供了 …

引用(个人学习笔记黑马学习)

1、引用的基本语法 #include <iostream> using namespace std;int main() {int a 10;//创建引用int& b a;cout << "a " << a << endl;cout << "b " << b << endl;b 100;cout << "a "…

docker 安装 MySQL5.7

1、拉取镜像 docker pull mysql:5.7 2、创建容器 docker run \ -d \ -p 3306:3306 \ --name mysql \ --privilegedtrue \ -v /var/docker/mysql/log:/var/log/mysql \ -v /var/docker/mysql/data:/var/lib/mysql \ -v /var/docker/mysql/conf:/etc/mysql/conf.d \ -e MYSQL_…

《TCP/IP网络编程》阅读笔记--Socket类型及协议设置

目录 1--协议的定义 2--Socket的创建 2-1--协议族&#xff08;Protocol Family&#xff09; 2-2--Socket类型&#xff08;Type&#xff09; 3--Linux下实现TCP Socket 3-1--服务器端 3-2--客户端 3-3--编译运行 4--Windows下实现 TCP Socket 4-1--TCP服务端 4-2--TC…

SpringWeb(SpringMVC)

目录 SpringWeb介绍 搭建 SpringWeb SpringWeb介绍 Spring Web是一个基于 Servlet API 构建的原始 web 框架&#xff0c;用于构建基于MVC模式的Web应用程序。在 web 层框架历经 Strust1&#xff0c;WebWork&#xff0c;Strust2 等诸多产品的历代更选 之后&#xff0c;目前业界普…

Gin 框架入门实战系列(一)

GIN介绍 Gin是一个golang的微框架,封装比较优雅,API友好,源码注释比较明确,具有快速灵活,容错方便等特点 对于golang而言,web框架的依赖要远比Python,Java之类的要小。自身的net/http足够简单,性能也非常不错 借助框架开发,不仅可以省去很多常用的封装带来的时间,…

ARM编程模型-指令流水线

流水线技术通过多个功能部件并行工作来缩短程序执行时间&#xff0c;提高处理器核的效率和吞吐率&#xff0c;从而成为微处理器设计中最为重要的技术之一。 1. 3级流水线 到ARM7为止的ARM处理器使用简单的3级流水线&#xff0c;它包括下列流水线级。 &#xff08;1&#xff0…

03-基础例程3

基础例程3 01、外部中断 ESP32的外部中断有上升沿、下降沿、低电平、高电平触发模式。 实验目的 使用外部中断功能实现按键控制LED的亮灭 按键按下为0。【即下降沿】 * 接线说明&#xff1a;按键模块-->ESP32 IO* (K1-K4)-->(14,27,26,25)* * …

探索未来金融科技 SCF新加坡举办启动盛会

金融科技的热潮涌向新加坡&#xff0c;令人瞩目的SCF金融公链启动会于8月13日隆重举行。这场盛宴不仅为金融科技领域注入了新的活力&#xff0c;更为广大投资者、合作伙伴以及热衷区块链发展的人士提供了一次宝贵的交流机会。 在SCF金融公链启动会上&#xff0c;William Thomps…

【数据结构篇】线性表1 --- 顺序表、链表 (万字详解!!)

前言&#xff1a;这篇博客我们重点讲 线性表中的顺序表、链表 线性表&#xff08;linear list&#xff09;是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构&#xff0c;常见的线性表&#xff1a;顺序表、链表、栈、队列... 线性表在逻辑上是…