当前位置: 首页 > news >正文

[数据结构]红黑树之插入操作(RBTree)

       这里只着重介绍插入操作的实现:)

一、红黑树的概念和性质

        红黑树(Red Black Tree)是一种自平衡的二叉搜索树。红黑树最初在1972年由Rudolf Bayer发明,当时被称为平衡二叉B树(symmetric binary B-trees)。随后,在1978年,Leo J. Guibas和Robert Sedgewick对其进行了修改,并正式命名为红黑树。

具有以下性质的二叉搜索树是红黑树:

1、所有结点要么是红色,要么是黑色

2、根结点永远是黑色的。

3、如果一个内部结点是红色的,那么它的两个子结点都必须是黑色的。

4、所有扩充的外部结点(空结点)都是黑色的。

5、所有叶子结点到根结点的路径都包含相同数目的黑色结点。

  一棵红黑树

        黑高度:从红黑树任意结点x出发(不包括结点x),到达一个外部结点(nil)的任一路径上黑结点的个数叫做结点x的黑高度(bh),也称为结点的阶(rank)。  一棵红黑树的黑高度指的是该树根节点的黑高度。上图和下图的红黑树黑高度都为2。

        为什么红黑树具有以上5个性质就可以让二叉搜索树自平衡呢?下面我们来看这张图:

        我把指向红色结点的指针视为有颜色的红色指针而指向黑色结点的指针视为有颜色的黑色指针来分析,例如上图中,50->20->10->nil的路径有3条指针,这条路径的叶子结点的高度为3

        现在假设一棵任意的红黑树,其黑高度为r。根据红黑树的所有叶子结点到根结点的路径都包含相同数目的黑色结点的性质,得出每条从根结点出发到nil路径上的黑色指针数目相同如果一条路径上的指针都是黑色,那么该路径肯定是红黑树里所能达到的最短路径,该条路径的指针数目为r,根结点到叶子结点高度为r。如果一条路径上的结点都是黑--黑-相间的指针,那么该路径肯定是红黑树里能达到最长的路径,该条路径的指针数目为2r,根结点到叶子结点高度为2r。

        所以可以知道从根结点位置出发,到外部结点nil的任意一条路径上的指针数目的范围是[r,2r]。设P,Q为红黑树中任意两条从根节点到外部结点nil的路径上的指针数目 ,P路径上的指针个数取值为[r,2r],Q路径上的指针个数取值也为[r,2r],那么总有P ≤ 2Q (或 Q ≤ 2P)

        PQ是任意两条路径,假设P就是红黑树中的最长路径,Q是红黑树中的最短路径,那么根据P ≤ 2Q就能得出结论:红黑树中的最长路径不超过最短路径的两倍,即红黑树的高度差不会超过r,这能保证红黑树永远保持近似平衡,这就是红黑树能够自平衡的关键。

二、红黑树的插入操作

        红黑树在插入一个结点时要不破坏红黑树的规则,根节点必须是黑色的,并且每条路径上的黑色结点个数都要相同,不能有相连的红色结点。所以我们在插入一个新的结点时都会选择插入红色的结点,因为这样不会影响这条路径上黑色结点的数目,但是新插入结点的双亲结点有可能也为红色,这就违反了红黑树不能有两个相连的红色结点的性质,这个时候就需要我们根据实际情况来调整红黑树的结构了。

下面出现的英文分别代表着对应的结点:

cur (c)—— 代表当前的红色结点

parent(p) —— cur的双亲结点

grand (g)—— parent的双亲结点

uncle (u)—— parent的兄弟结点

抽象结构:

        g

   p        u

c

(一)简单情况

情况一:插入结点前树为空,则新插入的结点颜色置为黑,并将其作为根节点。

情况二:在向上调整的过程中,cur结点的双亲结点parent颜色为黑,此时没有违反红黑树的性质,不需要处理,结束向上调整。

        下面的抽象图代表一颗完整的树或者一颗子树,圆形代表具体的结点,矩形代表结点的子树,如果cur为新插入的结点,那么子树(abcde)为空。

(二)只需变色的情况

        parent和uncle都为红色,cur为parent的左孩子或为右孩子。

        此时cur和parent违反红黑树不能有连续红色结点的性质,需要想办法将parent变黑。

        这种情况我们只需将parent和uncle改为黑色,grand改为红色即可。

        修改后以grand为根的这棵子树不再违反红黑树的性质,但是grand的颜色变红了,如果grand双亲节点不为空,则还需要向上考察grand和双亲结点的颜色是否也为红色,否则还需要进行调整。

       grand和双亲结点为红色,还需要进行继续向上调整:

        需要注意的是,调整完后,如果grand就为整个二叉搜索树的,则不继续再向上调整,但是结果根结点变为了红色,根据红黑树的性质,根是不能为红色的,为了保证根节点一定是黑色的,我们在每次进行插入操作后,将根节点的颜色置为黑色。

(三)旋转加变色的情况 

         cur和parent为红,uncle结点不存在或uncle颜色为黑色。

1、如果cur为parent的左孩子

        这种情况需要进行一个右单旋,以grand为轴,parent顺时针旋转,旋转后将grand和parent的颜色互换。

        经过旋转变色后,从前的子树变成了以parent为根的子树,该子树满足红黑树的性质,因为parent为黑,不再需要进行向上调整了,就算parent的双亲结点为红也无伤大雅。

2、如果cur为parent的右孩子

        这个时候我们需要进行左右双旋加变色,先以parent为轴,cur逆时针旋转后,再以grand为轴,cur顺时针旋转。然后交换cur和grand的颜色。

        经过旋转变色后,从前的子树变成了以cur为根的子树,该子树满足红黑树的性质,因为cur为黑,不再需要进行向上调整了,就算cur现在的双亲结点为红也无伤大雅。

(四)对称情况的处理

        parent和uncle都为红色。 cur为parent的左孩子或为右孩子。

        

         cur和parent为红,uncle结点不存在或uncle颜色为黑色。

1、如果cur为parent的右孩子

左单旋和变颜色

 2、如果cur为parent的左孩子

右左双旋和变颜色

三、红黑树插入的代码和测试代码

RBTree.h

#pragma once
#include <iostream>
using namespace std;//红黑树结点定义
template<class T>
struct RBTreeNode
{T _data;RBTreeNode* _pLeft;	//指向左孩子RBTreeNode* _pRight;	//指向右孩子RBTreeNode* _pParent;	//指向双亲结点bool _isRed;	//为false代表该结点为红,true代表该结点为黑RBTreeNode(const T& data = T()) :_data(data),_pLeft(nullptr), _pRight(nullptr), _pParent(nullptr),_isRed(true){}
};template<class T>
class RBTree
{typedef RBTreeNode<T> Node;
public:RBTree():_root(nullptr){}bool insert(const T& data){Node* pPos = nullptr;	//pPos记录新插入结点的双亲节点的位置Node* pCur = _root;while (pCur){pPos = pCur;if (data > pCur->_data)pCur = pCur->_pRight;else if (data < pCur->_data)pCur = pCur->_pLeft;elsereturn false;}//两种简单的情况之一:插入的结点作为根节点插入if (pPos == nullptr){//新插入的结点作为根结点_root = new Node(data);_root->_isRed = false;return true;}//插入新的结点pCur = new Node(data);if (data > pPos->_data)pPos->_pRight = pCur;elsepPos->_pLeft = pCur;pCur->_pParent = pPos;//向上调整Node* pParent = pPos;while (pParent && pParent->_isRed)	//当parent为黑或者调整到根节点为止结束{Node* pGrand = pParent->_pParent;if (pParent == pGrand->_pLeft)	//其中的一种对称情况{//		g//   p	   u//cNode* pUncle = pGrand->_pRight;if (pUncle && pUncle->_isRed)	//只需变色的情况:parent和uncle都为红色。 cur为parent的左孩子或为右孩子。{//这种情况我们只需将parent和uncle改为黑色,grand改为红色即可。pParent->_isRed = pUncle->_isRed = false;pGrand->_isRed = true;//继续向上调整pCur = pGrand;pParent = pGrand->_pParent;}else  //需要旋转+变色的情况:cur和parent为红,uncle结点不存在或uncle颜色为黑色。{if (pCur == pParent->_pLeft)	//右单旋{//这种情况需要进行一个右单旋,旋转后将parent和grand的颜色互换。//		g//   p	   u//crotateR(pGrand);pParent->_isRed = false;pGrand->_isRed = true;break;	//结束向上调整}else   //左右双旋{//这个时候我们需要进行左右双旋加变色,然后交换cur和grand的颜色。//		g//   p	   u//      crotateL(pParent);rotateR(pGrand);pCur->_isRed = false;pGrand->_isRed = true;break;	//结束向上调整}}}else  //对称的情况{//		g//   u	   p//            cNode* pUncle = pGrand->_pLeft;if (pUncle && pUncle->_isRed)	//只需变色的情况:parent和uncle都为红色。 cur为parent的左孩子或为右孩子。{//将parent和uncle变为红色pUncle->_isRed = pParent->_isRed = false;pGrand->_isRed = true;//继续向上调整pCur = pGrand;pParent = pGrand->_pParent;}else  //需要旋转加变色的情况{if (pCur == pParent->_pRight)  //cur为parent的右孩子,左单旋{//		g//   u	   p//            crotateL(pGrand);pParent->_isRed = false;pGrand->_isRed = true;break;	//结束向上调整}else  //右左单旋{//		g//   u	   p//      crotateR(pParent);rotateL(pGrand);pCur->_isRed = false;pGrand->_isRed = true;break;}}}}_root->_isRed = false;	//将根节点置为黑return true;}// 查找Node* find(const T& data){Node* pCur = _root;	while (pCur){if (data > pCur->_data)pCur = pCur->_pRight;else if (data < pCur->_data)pCur = pCur->_pLeft;elsereturn pCur;}return nullptr;}// 检测红黑树是否为有效的红黑树bool checkRBTree(){int pathBlack = 0;Node* pCur = _root;while (pCur)	//找最左侧那条路径的黑色结点总数作为基准值{if (pCur->_isRed == false)pathBlack++;pCur = pCur->_pLeft;}return _checkRBTree(_root, 0, pathBlack);}//计算树的高度int height(){return _height(_root);}//计算结点个数int size(){return _size(_root);}//中序遍历并打印结点数据void inOrder(){_inOrder(_root);}
private://blackCount是根节点到该结点路径上的所有黑色结点的数量,pathBlack是基准值bool _checkRBTree(Node* pRoot, size_t blackCount, size_t pathBlack){if (pRoot == nullptr)	//到达外部结点nil{if (blackCount == pathBlack)//检查该路径上的黑色结点数量和基准值是否一样return true;else{cout << "data:" << pRoot->_data;cout << " blackCount = " << blackCount << "and pathBlack = " << pathBlack << endl;return false;}}if (pRoot->_isRed && pRoot->_pParent->_isRed)	//如果该结点和其双亲结点都为红色{cout << "data:" << pRoot->_data;cout << " 有相邻的红色" << endl;return false;}if (pRoot->_isRed == false)	//统计到该路径为止黑色结点的数目blackCount++;return _checkRBTree(pRoot->_pLeft, blackCount, pathBlack) && _checkRBTree(pRoot->_pRight, blackCount, pathBlack);}// 左单旋void rotateL(Node* pParent){//     grandparent//		 parent//				sub//			subLNode* pGrandParent = pParent->_pParent;Node* sub = pParent->_pRight;Node* subL = sub->_pLeft;pParent->_pRight = subL;if (subL)subL->_pParent = pParent;sub->_pLeft = pParent;pParent->_pParent = sub;if (pGrandParent == nullptr)_root = sub;else if (pGrandParent->_pLeft == pParent)pGrandParent->_pLeft = sub;elsepGrandParent->_pRight = sub;sub->_pParent = pGrandParent;}// 右单旋void rotateR(Node* pParent){//	   grandparent//		 parent//	 sub//		 subRNode* pGrandParent = pParent->_pParent;Node* sub = pParent->_pLeft;Node* subR = sub->_pRight;pParent->_pLeft = subR;if (subR)subR->_pParent = pParent;sub->_pRight = pParent;pParent->_pParent = sub;if (pGrandParent == nullptr)_root = sub;else if (pGrandParent->_pLeft == pParent)pGrandParent->_pLeft = sub;elsepGrandParent->_pRight = sub;sub->_pParent = pGrandParent;}//中序遍历void _inOrder(Node* root){if (root == nullptr)return;_inOrder(root->_pLeft);cout << root->_data << " ";_inOrder(root->_pRight);}int _height(Node* root)//计算树的高度{if (root == nullptr)return 0;int leftHeight = _height(root->_pLeft);int rightHeight = _height(root->_pRight);return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;}int _size(Node* root)	//计算树的结点个数{if (root == nullptr)return 0;return _size(root->_pLeft) + _size(root->_pRight) + 1;}private:Node* _root;
};

Main.c

#include "RBTree.h"//压力测试
void performanceTest()
{const int N = 10000;RBTree<int> tree;clock_t start1 = clock();for (int i = 0; i < N; i++){tree.insert(i);	//插入有序序列或随机值都行}cout << tree.checkRBTree() << endl;clock_t end1 = clock();cout << "插入用时:" << (float)(end1 - start1) / CLOCKS_PER_SEC << "秒" << endl;cout << "高度:" << tree.height() << endl;cout << "结点个数" << tree.size() << endl;int count = 0;start1 = clock();for (int i = 0; i < N; i++){if (!tree.find(i))count++;}end1 = clock();cout << "查找所有值用时:" << (float)(end1 - start1) / CLOCKS_PER_SEC << "秒" << endl;cout << count << "个找不到!" << endl;//查找随机值count = 0;start1 = clock();for (int i = 0; i < N; i++){if (!tree.find(rand()))count++;}end1 = clock();cout << "查找随机值用时:" << (float)(end1 - start1) / CLOCKS_PER_SEC << "秒" << endl;cout << count << "个找不到!" << endl;cout << tree.size() << endl;
}int main()
{performanceTest();return 0;
}

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 深圳又有5家企业高新企业资质被取消?
  • 3个恢复方法详解:iPhone手机快速找回备忘录
  • 大数据-122 - Flink Time Watermark Java代码测试实现Tumbling Window
  • 拥抱分布式云:云基础设施的下个新时代
  • 11、Django Admin启用对计算字段的过滤
  • [ios]准备好app后使用xcode发布ios操作
  • 用SpringBoot API实现识别pdf文件是否含有表格
  • AI建模——AI生成3D内容算法产品介绍与模型免费下载
  • 【人工智能/机器学习/机器人】数学基础-学习笔记
  • Z Product | AI教母李飞飞AI创业,4 个月估值达 10 亿美金,目标是使AI能够像人类一样理解和推理三维物理世界
  • 口语笔记——定语
  • 进程管理中的三态模型
  • 828华为云征文 | Flexus X实例与华为云EulerOS的Tomcat安装指南
  • 智能监测,守护未来:QY-19 GNSS位移监测站
  • 揭秘IP地址与SSL证书:构建数字世界的信任桥梁
  • (三)从jvm层面了解线程的启动和停止
  • Git初体验
  • Hibernate最全面试题
  • k个最大的数及变种小结
  • node.js
  • Promise面试题,控制异步流程
  • Terraform入门 - 1. 安装Terraform
  • Vue 重置组件到初始状态
  • 彻底搞懂浏览器Event-loop
  • 更好理解的面向对象的Javascript 1 —— 动态类型和多态
  • 基于阿里云移动推送的移动应用推送模式最佳实践
  • 前端每日实战 2018 年 7 月份项目汇总(共 29 个项目)
  • 湖北分布式智能数据采集方法有哪些?
  • ​力扣解法汇总946-验证栈序列
  • ## 基础知识
  • ### Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLTr
  • #[Composer学习笔记]Part1:安装composer并通过composer创建一个项目
  • ${ }的特别功能
  • (152)时序收敛--->(02)时序收敛二
  • (LNMP) How To Install Linux, nginx, MySQL, PHP
  • (超详细)2-YOLOV5改进-添加SimAM注意力机制
  • (分布式缓存)Redis持久化
  • (附源码)ssm本科教学合格评估管理系统 毕业设计 180916
  • (三)c52学习之旅-点亮LED灯
  • (一)80c52学习之旅-起始篇
  • (一)utf8mb4_general_ci 和 utf8mb4_unicode_ci 适用排序和比较规则场景
  • (已解决)报错:Could not load the Qt platform plugin “xcb“
  • (转)ABI是什么
  • (转)我也是一只IT小小鸟
  • (转载)CentOS查看系统信息|CentOS查看命令
  • .360、.halo勒索病毒的最新威胁:如何恢复您的数据?
  • .mp4格式的视频为何不能通过video标签在chrome浏览器中播放?
  • .net 4.0发布后不能正常显示图片问题
  • .net core 客户端缓存、服务器端响应缓存、服务器内存缓存
  • .net core控制台应用程序初识
  • .NET DevOps 接入指南 | 1. GitLab 安装
  • .net 后台导出excel ,word
  • .NET/C# 反射的的性能数据,以及高性能开发建议(反射获取 Attribute 和反射调用方法)
  • @Bean有哪些属性
  • @DataRedisTest测试redis从未如此丝滑