红黑树概念及其相关操作的实现

红黑树的概念

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

红黑树的性质

  1. 每个结点不是红色就是黑色
  2. 根节点是黑色的 ,空树也是红黑树
  3. 如果一个节点是红色的,则它的两个孩子结点是黑色的 ,不可能出现连在一起的红色结点
  4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点 ,每条路径里黑色结点个数是相等的
  5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)

问题

为什么满足上面的性质,红黑树就能保证:其最长路径中节点个数不会超过最短路径节点个数的两 倍?
在这里插入图片描述
极端情况下,刚好是两倍,但是这种极端情况不存在。因为根结点是黑的,那么第二层的两个结点都必须是红色的。

红黑树的结构

在这里插入图片描述

红黑树结点的定义

enum COLOR{RED,BLACK};template<class T>
struct RBTreeNode
{RBTreeNode(const T& data = T(), COLOR color = RED):_pLeft(nullptr), _pRight(nullptr), _pParent(nullptr), _data(data), _color(color)	{}RBTreeNode<T>* _pLeft;RBTreeNode<T>* _pRight;RBTreeNode<T>* _pParent;T _data;COLOR _color;
};
为什么要将结点的默认颜色给成红色的?

因为如果默认颜色是黑色的话,往已经是一颗红黑树里插入的话,性质4一定遭到破坏,所以给成红色,有些情况下破坏,有些情况下不被破坏

红黑树的插入

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

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

template<class T>
class RBTree
{typedef RBTreeNode<T> Node;
public:RBTree(){_pHead = new Node;_pHead->_pLeft = _pHead;_pHead->_pRight = _pHead;//构造里已经将双亲给成空}bool Insert(const T&data){Node* pRoot = GetRoot();if (nullptr == pRoot) //树为空,创建根结点{pRoot = new Node(data,BLACK);pRoot->_pParent = _pHead;//只有一个结点,head就是根节点的双亲_pHead->_pLeft = pRoot; //改变左右指针域_pHead->_pRight = pRoot;return true;}else{//说明树已经不为空了//1.按照二叉搜索树的性质找到带插入结点在红黑树的位置Node* pCur = pRoot;Node* pParent = nullptr;while (pCur){pParent = pCur;//标记双亲位置if (data < pCur->_data)pCur = pCur->_pLeft;else if (data>pCur->_data)pCur = pCur->_pRight;else//相同不插入return false;}//2. 插入新结点pCur = new Node(data);if (data < pParent->_data)pParent->_pLeft = pCur;else{pParent->_pRight = pCur;}//3. 更新双亲位置pCur->_pParent = pParent;//4.检测:是否新结点插入后连在一起的红色结点if (RED == pParent->_color){//调整//检测新节点插入后,红黑树的性质是否造到破坏}}//5.调整头结点的左右指针域//保证根节点是黑色pRoot->_color = BLACK;_pHead->_pLeft=leftMost();_pHead->_pRight = RightMost();return true;}Node* LeftMost(){//得到根节点Node* pRoot = GetRoot();if (nullptr == pRoot)return _pHead;Node* pCur = pRoot;//找到最左侧结点while (pCur->_pLeft)pCur = pCur->_pLeft;return pCur;}Node* RightMost(){//得到根节点Node* pRoot = GetRoot();if (nullptr == pRoot)return _pHead;Node* pCur = pRoot;//找到最右侧结点while (pCur->_pRight)pCur = pCur->_pRight;return pCur;}
protected:Node*& GetRoot()  //head是new出来的,head存在parent一定存在,按引用方式返回没有问题{//得到根节点,也就是头结点的下一个结点return _pHead->_pParent;}
private:Node* _pHead;
};

2. 检测新节点插入后,红黑树的性质是否造到破坏

因为新节点的默认颜色是红色,因此:如果其双亲节点的颜色是黑色,没有违反红黑树任何性质,则不需要调整;但当新插入节点的双亲节点颜色为红色时,就违反了性质三不能有连在一起的红色节点此时需要对红黑树分情况来讨论
cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点

2.1 cur为红,p为红,g为黑,u存在且为红

解决方式:将p,u改为黑,g改为红,然后把g当成cur,继续向上调整。
在这里插入图片描述

2.2 cur为红,p为红,g为黑,u不存在/u为黑

p为g的左孩子,cur为p的左孩子,则进行右单旋转;相反,
p为g的右孩子,cur为p的右孩子,则进行左单旋转
p、g变色–p变黑,g变红

在这里插入图片描述

  • 如果u不存在,假设cur本来就存在,cur和双亲比然是黑的,因为两个红的不能连在一起,那么这条路径里就有两个黑色,所以不满足性质4,cur所以一定是新插入的结点
  • 如果u存在且为黑色,右侧路径里有两个黑色路径,因为两条路径黑色结点必须一样。新节点一插入,插入到cur的子树中,导致子树中不满足情况。向上调整时,把cur改成黑色。

2.3 cur为红,p为红,g为黑,u不存在/u为黑

在这里插入图片描述

红黑树的验证

红黑树的检测分为两步:

  1. 检测其是否满足二叉搜索树(中序遍历是否为有序序列)
  2. 检测其是否满足红黑树的性质
template<class T>
class RBTree
{typedef RBTreeNode<T> Node;
public:RBTree(){_pHead = new Node;_pHead->_pLeft = _pHead;_pHead->_pRight = _pHead;//构造里已经将双亲给成空}bool Insert(const T&data){Node*& pRoot = GetRoot(); //必须以引用的方式进行接受if (nullptr == pRoot) //树为空,创建根结点{pRoot = new Node(data,BLACK);pRoot->_pParent = _pHead;//只有一个结点,head就是根节点的双亲_pHead->_pLeft = pRoot; //改变左右指针域_pHead->_pRight = pRoot;return true;}else{//说明树已经不为空了//1.按照二叉搜索树的性质找到带插入结点在红黑树的位置Node* pCur = pRoot;Node* pParent = nullptr;while (pCur){pParent = pCur;//标记双亲位置if (data < pCur->_data)pCur = pCur->_pLeft;else if (data > pCur->_data)pCur = pCur->_pRight;else//相同不插入return false;}//2. 插入新结点pCur = new Node(data);if (data < pParent->_data)pParent->_pLeft = pCur;elsepParent->_pRight = pCur;//3. 更新双亲位置pCur->_pParent = pParent;//以上没错//4.检测:是否新结点插入后连在一起的红色结点while (pParent!=_pHead && RED == pParent->_color){Node* granderFather = pParent->_pParent;if (pParent == granderFather->_pLeft){//叔叔结点在右侧Node* uncle = granderFather->_pRight;//情况一:叔叔结点存在,且为红if (uncle && RED == uncle->_color){pParent->_color = BLACK;uncle->_color = BLACK;granderFather->_color = RED;pCur = granderFather;pParent = pCur->_pParent;}//以上没问题else{//情况三if (pCur == pParent->_pRight) //情况三{//转变成情况二RotateLeft(pParent);swap(pParent, pCur);}//情况二pParent->_color = BLACK;granderFather->_color = RED;RotateRight(granderFather);}//以上没问题}else{//叔叔结点在左侧Node* uncle = granderFather->_pLeft;//情况一的反情况if (uncle && uncle->_color == RED){pParent->_color = BLACK;uncle->_color = BLACK;granderFather->_color = RED;pCur = granderFather;pParent = pCur->_pParent;}//以上没问题else{//情况三的反情况if (pCur == pParent->_pLeft)	/**/{//情况三的反情况变成情况二的反情况RotateRight(pParent);swap(pParent, pCur);}//情况二反情况处理pParent->_color = BLACK;granderFather->_color = RED;RotateLeft(granderFather);}//以上没问题}}}//5.调整头结点的左右指针域//保证根节点是黑色pRoot->_color = BLACK;_pHead->_pLeft = LeftMost();_pHead->_pRight = RightMost();return true;}void InOrder(){_InOrder(GetRoot());}//检测红黑树bool IsValidRBTree(){Node* pRoot = GetRoot();if (nullptr == pRoot)return true;if (pRoot->_color != BLACK){cout << "违反性质2:根结点颜色必须为黑色" << endl;return false;}//获取一条路径中结点的个数size_t blackCount = 0; //基准值Node* pCur = pRoot;while (pCur){if (pCur->_color == BLACK)blackCount++;pCur = pCur->_pLeft;}size_t pathBlack = 0; //每条路径中的黑色结点个数return _IsValidRBTree(pRoot, blackCount,pathBlack);}
protected:bool _IsValidRBTree(Node* pRoot, size_t blackCount, size_t pathBlack){if (nullptr == pRoot)return true;if (pRoot->_color == BLACK)pathBlack++;//检测性质3Node* pParent = pRoot->_pParent;if (pParent != _pHead && pParent->_color == RED&&pRoot->_color == RED){cout << "违反性质3:不能有连在一起的红色结点" << endl;return false;}if (nullptr == pRoot->_pLeft&&nullptr == pRoot->_pRight){//一条路径到叶子if (blackCount != pathBlack){cout << "违反了性质4:每条路径中黑色结点个数必须相同" << endl;return false;}}return _IsValidRBTree(pRoot->_pLeft, blackCount, pathBlack) &&_IsValidRBTree(pRoot->_pRight, blackCount, pathBlack);}Node* LeftMost(){//得到根节点Node* pRoot = GetRoot();if (nullptr == pRoot)return _pHead;Node* pCur = pRoot;//找到最左侧结点while (pCur->_pLeft)pCur = pCur->_pLeft;return pCur;}Node* RightMost(){//得到根节点Node* pRoot = GetRoot();if (nullptr == pRoot)return _pHead;Node* pCur = pRoot;//找到最右侧结点while (pCur->_pRight)pCur = pCur->_pRight;return pCur;}void RotateLeft(Node* pParent){Node* pSubR = pParent->_pRight;Node* pSubRL = pSubR->_pLeft;pParent->_pRight = pSubRL;if (pSubRL)pSubRL->_pParent = pParent;pSubR->_pLeft = pParent;Node* pPParent = pParent->_pParent;pParent->_pParent = pSubR;pSubR->_pParent = pPParent;if (pPParent == _pHead)GetRoot() = pSubR;else{if (pPParent->_pLeft == pParent)pPParent->_pLeft = pSubR;elsepPParent->_pRight = pSubR;}}void RotateRight(Node* pParent){Node* pSubL = pParent->_pLeft;Node* pSubLR = pSubL->_pRight;pParent->_pLeft = pSubLR;if (pSubLR)pSubLR->_pParent = pParent;pSubL->_pRight = pParent;Node* pPParent = pParent->_pParent;pParent->_pParent = pSubL;pSubL->_pParent = pPParent;//pParent是根结点if (pPParent == _pHead)GetRoot() = pSubL;else{//非根结点if (pPParent->_pLeft == pParent)pPParent->_pLeft = pSubL;elsepPParent->_pRight = pSubL;}}Node*& GetRoot()  //head是new出来的,head存在parent一定存在,按引用方式返回没有问题{//得到根节点,也就是头结点的下一个结点return _pHead->_pParent;}void _InOrder(Node* pRoot){if (pRoot){_InOrder(pRoot->_pLeft);cout << pRoot->_data << " ";_InOrder(pRoot->_pRight);}}
private:Node* _pHead;
};void TestRBTree()
{int array[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };RBTree<int>t;for (auto e : array)t.Insert(e);t.InOrder();if (t.IsValidRBTree()){cout << "t is vaild rbTree" << endl;}elsecout << "t is not vaild rbTree" << endl;
}

在这里插入图片描述

红黑树的删除

参考链接1
参考链接2

红黑树与AVL树的比较

红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O( log2N),红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多。

红黑树的应用

  1. Java中的java.util.TreeMap,java.util.TreeSet
  2. C++ STL中的:map,multimap,multiset
  3. .NET中的:SortedDictionary,SortedSet 等

模拟实现map和set

模拟实现

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

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

相关文章

模拟实现STL中map和set容器

红黑树的迭代器 //红黑树的迭代器 template<class T> struct RBTreeIterator {typedef RBTreeNode<T>Node;typedef RBTreeIterator<T> Self; public:RBTreeIterator(Node* pNode nullptr):_pNode(pNode){}//具有指针操作T& operator*(){return _pNode-…

排序上---(排序概念,常见排序算法,直接插入,希尔排序,直接选择排序,堆排序)

排序的概念 排序&#xff1a;所谓排序&#xff0c;就是使一串记录&#xff0c;按照其中的某个或某些关键字的大小&#xff0c;递增或递减的排列起来的操作。稳定性&#xff1a;假定在待排序的记录序列中&#xff0c;存在多个具有相同的关键字的记录&#xff0c;若经过排序&…

排序下---(冒泡排序,快速排序,快速排序优化,快速排序非递归,归并排序,计数排序)

排序上 排序上 交换类排序 基本思想&#xff1a;所谓交换&#xff0c;就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置&#xff0c;交换排序的特点是&#xff1a;将键值较大的记录向序列的尾部移动&#xff0c;键值较小的记录向序列的前部移动。 冒泡…

哈希的概念及其操作

哈希概念 顺序结构以及平衡树中&#xff0c;元素关键码与其存储位置之间没有对应的关系&#xff0c;因此在查找一个元素时&#xff0c;必须要经过关键码的多次比较。顺序查找时间复杂度为O(N)&#xff0c;平衡树中为树的高度&#xff0c;即O( Log2N)&#xff0c;搜索的效率取决…

软件工程---1.概述

软件的特征 抽象&#xff1a; 不可触摸&#xff0c;逻辑实体&#xff0c;可记录&#xff0c;但看不到复制成本低&#xff1a;不受物质材料的限制&#xff0c;不受物理定律或加工过程的制约&#xff0c;与开发成本相比&#xff0c;复制成本很低无折旧、受硬件制约、未完全摆脱手…

软件工程---2.软件过程

三个模型 瀑布模型增量模型集成和配置模型 没有适用于所有不同类型软件开发的过程模型。 瀑布模型 需求定义系统和软件的设计实现与单元测试集成与系统测试运行与维护 瀑布模型的特征 从上一项活动中接受该项活动的工作成果&#xff08;工作产品&#xff09;&#xff0c;作…

软件工程---4.需求工程

需求工程定义 找出、分析、文档化并且检查需求的过程被称为需求工程 需求的两个描述层次 用户需求&#xff0c;指高层的抽象需求。使用自然语言、图形描述需求。系统需求&#xff0c;指底层的详细需求。使用系统需求文档&#xff08;有时被称为功能规格说明&#xff09;应该…

软件工程---5.系统建模

从不同视角对系统建模 外部视角&#xff0c;上下文模型&#xff0c;对系统上下文或环境建模交互视角&#xff0c;交互模型&#xff08;功能模型&#xff09;&#xff0c;对系统与参与者或系统内构件之间的交互建模结构视角&#xff0c;结构模型&#xff08;静态模型&#xff0…

软件工程---6.体系结构设计

体系结构模型是什么&#xff1f; 体系结构模型&#xff0c;该模型描述系统如何被组织为一组相互通信的构件 体系结构分类 小体系结构关注单个程序的体系结构。在这个层次上&#xff0c;我们关注单个的程序是如何补分解为构件的。大体系结构关注包括其他系统、程序和程序构件…

【剑指offer】_07 矩形覆盖

题目描述 我们可以用21的小矩形横着或者竖着去覆盖更大的矩形。请问用n个21的小矩形无重叠地覆盖一个2*n的大矩形&#xff0c;总共有多少种方法&#xff1f; 解题思路 依旧是斐波那契数列 2n的大矩形&#xff0c;和n个21的小矩形 其中target*2为大矩阵的大小 有以下几种情形…

软件工程---07.设计与实现

软件设计和软件实现 软件设计是一个创造性的活动&#xff0c;在此活动中需要基于客户需求识别软件构件及其关系。软件实现是将设计实现为一个程序的过程 为开发一个系统设计&#xff0c;你需要 理解并定义上下文模型以及系统的外部交互设计系统体系结构识别系统中的主要对象…

软件工程---15.软件复用

复用的图(牢记) 软件复用的好处 开发加速有效的专家利用提高可依赖性降低开发成本降低过程风险符合标准 软件复用的缺点 创建&#xff0c;维护以及使用一个构件库查找&#xff0c;理解以及适配可复用构件维护成本增加缺少工具支持“不是在这里发明的”综合症 应用框架 现在…

软件工程---16.基于构件的软件工程

CBSE CBSE是定义、实现、集成或组装松散耦合的独立构件成为系统的过程。 基于构件的软件工程的要素有: 完全由接口进行规格说明的独立构件。构件标准使构件集成变得更为容易。中间件为构件集成提供软件支持。开发过程适合基于构件的软件工程。 CBSE的设计原则 构件是独立的…

软件工程---17.分布式软件工程

分布式系统的5个优点 资源共享开放性并发性可伸缩性容错性 分布式计算中必须考虑的设计问题 透明性&#xff1a;隐藏底层分布 开放性 可伸缩性 三个维度 规模&#xff1a;又分为增强扩展(单挑)&#xff0c;增加扩展(群殴)分布可靠性 信息安全性 主要防止以下类型的攻击 拦…

软件工程---18.面向服务的软件工程

什么是Web服务 一个松耦合、可复用的软件构件&#xff0c;封装了离散的功能&#xff0c;该功能是分布式的并且可以被程序访问。Web服务是通过标准互联网和基于XML的协议被访问的服务。 服务和软件构件之间的一个重要的区别是 服务应该总是独立的和松耦合的Web 服务没有“请求…

【剑指offer】_09二叉搜索树的后序遍历序列

题目描述 输入一个整数数组&#xff0c;判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。 解题思路 比如下面的这棵二叉搜索树 它的后序遍历为0214369875&#xff1b; 我们设当前根节点为root; 第一次…

【剑指offer】_12 数组中的逆序对

题目描述 在数组中的两个数字&#xff0c;如果前面一个数字大于后面的数字&#xff0c;则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007 解题思路 剑指offer的解法 看到这个题目&#xff0…

详解Linux下通过yum安装Mariadb/MySQL数据库(腾讯云也适用)

1. 安装Mariadb 安装命令 yum -y install mariadb mariadb-server安装完成MariaDB&#xff0c;首先启动MariaDB systemctl start mariadb设置开机启动 systemctl enable mariadbMariaDB的相关简单配置 此命令进入到配置相关界面 mysql_secure_installation首先是设置密码…

海量数据处理(位图和布隆过滤器)

哈希切割 给一个超过100G大小的log file, log中存着IP地址, 设计算法找到出现次数最多的IP地址&#xff1f; 与上题条件相同&#xff0c;如何找到top K的IP&#xff1f;如何直接用Linux系统命令实现 解决思路 找到出现次数最多的IP地址 要找到前TopK的IP地址&#xff0c;就…

C++中的lambda表达式和线程库

98中的一个例子 如果想要对一个数据集合中的元素进行排序&#xff0c;可以使用std::sort方法 #include <algorithm> #include <functional> int main() {int array[] {4,1,8,5,3,7,0,9,2,6};// 默认按照小于比较&#xff0c;排出来结果是升序std::sort(array, a…