前言:
我们都知道二叉搜索树,是一种不错的用于搜索的数据结构,如果二叉搜索树越接近完全二叉树,那么它的效率就会也高,但是它也存在的致命的缺陷,在最坏的情况下,二叉搜索树会退化成为单链表,这时,二叉搜索树也就丧失了它的搜索能力。因此为了解决它的问题,后面就有人提出了两种改进结构AVL树和红黑树,让我们一起来学习一下其中的一种改进方案:红黑树吧!
目录
1.什么是红黑树
2.红黑树的特点
3.红黑树的模拟实现
3.1红黑树的节点结构
3.2红黑色的插入操作
3.3红黑色的验证
3.4红黑树的查找
3.5红黑树的删除
3.5.1删除详解
3.5.2删除的代码实现
3.6测试代码
3.7全部代码
4.红黑树与AVL树的比较
5.红黑树的应用
3.2红黑色的插入操作
3.3红黑色的验证
3.4红黑树的查找
4.红黑色与AVL树的比较
5.红黑色的应用
1.什么是红黑树
红黑树实际上就是一种二叉搜索树,但在每个节点上都增加了一个存储位表示节点的颜色,可以是red或者black。通过对任意一条从根节点到叶子节点的路径上各个节点着色方式的限定,红黑树确保没有一条路径比其它路径长出两倍。因而是接近平衡的。
一颗二叉搜书树,越平衡搜索时效率就越高,显然红黑色的平衡性要比AVL树略差,但是经过大量的实验证明红黑色的效率还是不错的,仍能达到O(logN),这个我也不是特别了解只是在别的树上看到的,我没有做过这种类似的大量的实验来证明它的性能,总之:虽然红黑树不是严格的平衡树,但是它的效率也是很高的,不管怎么样你要知道它的时间复杂度是小于2O(logN) !
2.红黑树的特点
1.每个节点不是红色就是黑色
2.根节点是黑色
3.如果一个节点是红色,那么它的两个孩子是黑色
4.对于每个节点,从该节点到其所有后代叶子节点的简单路径上,均包含相同数目的黑色节点。
5.每个叶子节点都是黑色的(此处的叶子节点指的是空节点)
一共有五条,但是归结起来就三点:1.根节点是黑色的,2.没有连续的红色节点,3. 叶子节点(空节点)是黑色的。
咋一看红黑树怎么这么多要求呢,看的人头都是晕的,简单来分析一下,首先每个节点都存在两种颜色,或者是红色或者是黑色,凡是空节点都是黑色的也就是这里说的叶子节点。根节点是黑色的。这第1,2,5看起来还好理解,第3,4就不是很好理解了。第3:说明红色节点是不连续的,而第4:说明每个路径上面的黑色节点是相同的,比如:从根开始到1这个节点到叶子节点的黑色节点的个数是和过6这个节点到叶子节点的黑色节点的个数是相同的都是3个。因为如果满足上述的这些条件,那么就可以保证最长路径上的节点个数不会超过最短路径上的节点个数的两倍。
它是如何保证的最长路径是小于等于最短路径的呢?因为叶子节点是黑色的,根节点也是黑色的,且没有连续的红色节点,那么最长的路径一定是黑红相间的,而最短的路径一定是都是由黑色节点组成的。 每条路径上黑色节点的个数是相同的,那么最长路径的节点数减去最短路径的节点数一定是最长路径上红色节点的个数,在上述的种种规定下,一条路径上的红色节点数一定是小于等于黑色节点的个数的,所以就保证了,最长路径上一定小于或者等于2倍的最短路径。
3.红黑树的模拟实现
实现红黑树,对于平衡树的旋转操作是必须要了解的,所以如果对平衡树的旋转不了解的同学建议先话三分钟了解一下平衡树 的旋转。(剩下的6分钟应该还是可以学会红黑树的...)
3.1红黑树的节点结构
红黑树是二叉搜索树的结构,它的每个节点都是由节点存储的值,节点的指针和表示节点的颜色的枚举类型组成的。且树的结构是三叉链,这样结构方便从子节点逆着寻找父节点。
当然还可以有其它的实现方式。
//红黑树的节点有两种状态//RED-BLACKenum Colour{BLACK,RED,};template<class K,class V>struct RBTreeNode{//红黑色是三叉链//+节点的状态+存储在节点中的指针RBTreeNode<K, V> _left;RBTreeNode<K, V> _right;RBTreeNode<K, V> _prev;pair<K, V> _Key;Colour _col;//构造函数RBTreeNode(const pair<K,V> data):_left(nullptr),_right(nullptr),_prev(nullptr),_Key(data),_col(RED){ }};
3.2红黑色的插入操作
由于性质的约束,红黑色新插入的节不点可以是黑色的,红黑树新插入的节点都是红色的为什么呢?如果新插入点节点是黑色的会破坏第4点性质,破坏后是不容易修正的,但是如果插入的节点是红色的节点,这时候如果它的父节点是红色的就破坏了第3点性质,所以这时候就要通过一些旋转和节点的变色来进行处理,使得它满足第3点性质。简而言之就是如果插入的是黑色的节点是一定会破坏的第4点的,插入的是红色的节点只是有可能会破坏第3点,并且要保证每条路径上有相同数量的黑色节点是不太容易的,而让红色的节点不连续是比较简单的,两害相权取其轻,所以在插入时选择插入红色的节点。以下我将会列出所出现的所有情况。另外为了叙述方便我们给新插入的节点标为C,父节点为P,祖父节点为G,叔叔节点为U。
情况1:树为空,此时需要将根节点赋值给新插入的节点,并且将根节点的颜色变黑,(恢复第1点性质)。
情况2:插入节点后,这个节点的父节点是黑色的,此时不需要处理。结束插入。
前面两种其实没有什么好看的,因为很简单,需要注意的是接下来的三种。
情况3:插入节点后,C为红,P为红,如果U为红,此时G一定存在且为黑(不然在插入之前就不符合红黑色的特点了),这种情况一定破坏第三点,需要将P和U变为黑色,G变为红色,然后继续进行判断属于哪种情况,这种状态不需要旋转只要进行变色处理就可以了。
情况四:C为红,P为红,U不存在或者U为黑。C为P的左孩子,且P为G左孩子(或者C为P的右孩子,且P为G右孩子,总之是同向的),这时候进行右单旋(左单旋)。然后将P变为黑色,G变为红色就结束了。如图:
情况五:C为红,P为红,U不存在或者U为黑。C为P的左孩子,P是G的右孩子(或者C为P的右孩子,P为G的左孩子,总之它们是反向的),这时候进行进行两次变化操作,首先在P的位置进行左旋,这时候不变节点的颜色,(这时候实际上已经转换为情况4,按照情况4 的操作就行)然后再G的位置进行右旋,将G变为红色,将C变为黑色。
如图: 实际上由于P,C都为红色,旋转1次后,不违反第4点性质。然后就变为情景4.
插入节点后检测是否破坏红黑树的性质。 显然情况1,2是没有破坏红黑树的性质的。只有情况3,4,5破坏了红黑树的性质。
实现代码:
bool Insert(const V& data){//如果根节点为空直接插入并初始化根节点if (_root == nullptr){_root = new Node(data);_root->_col = BLACK;//将根节点变黑}Node* cur = _root;Node* parent = nullptr;while (cur){//新插入的节点都是红色的,if (cur->_data == data){//节点中存在data不需要进行插入return false;}else if (data > cur->_data){parent = cur;cur = cur->_right;}else if (data < cur->_data){parent = cur;cur = cur->_left;}}//申请新节点进行插入cur = new Node(data);cur->_parent = parent;cur->_col = RED;if (cur->_data > parent->_data){parent->_right = cur;}else{parent->_left = cur;}while (parent && parent->_col == RED)//插入cur之后只有当parent存在且为红色才需要处理{//如果parent存在且为红色,那么说明它的parent的父亲一定存在,且为黑色Node* grandfather = parent->_parent;//这时候需要根据uncle的颜色进行判断来区分是哪种情况然后再进行处理if (parent == grandfather->_left){Node* uncle = grandfather->_right;if (uncle && uncle->_col == RED){//如果舅舅存在且为红,需要将parent和uncle都变为黑色,然后再将grandfather变为红色//继续迭代处理看是属于哪种情况uncle->_col = parent->_col = BLACK;grandfather->_col = RED;//迭代cur = grandfather;parent = cur->_parent;}else{//如果uncle不存在或者uncle存在且为黑色,就要进行其它的处理了if (cur == parent->_right)//parent == grandfather->_left{//这时候需要进行双旋//先进行一次左旋RotateL(parent);//在parent位置::swap(cur, parent);//交换两个指针//通过上面的处理可以将左右双旋变为右单旋}//这里处理的就是右单旋的情况,RotateR(grandfather);//变色parent->_col = BLACK;grandfather->_col = RED;break;}}else//parent == grandfather->_right{Node* uncle = grandfather->_left;//这里也是一样的,分为uncle为红色和不为红色两种情况if (uncle && uncle->_col == RED){//如果uncle为红色,就要将parent和uncle都变为黑色,将grandfather变为红色//然后迭代进行其它处理parent->_col = uncle->_col = BLACK;grandfather->_col = RED;//迭代cur = grandfather;parent = cur->_parent;}else{//这里处理的是uncle为黑色或者uncle不存在的情况//需要旋转+变色进行处理if (cur == parent->_left)//parent == grandfather->_right{//右单旋RotateR(parent);::swap(cur, parent);//这样处理就可以将需要双旋的场景转化为单旋}RotateL(grandfather);//变色parent->_col = BLACK;grandfather->_col = RED;break;}}}_root->_col = BLACK;//不管怎么样最后将根节点变为黑色return true;}//左右单旋的代码void RotateL(Node*parent){//左单旋Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)//subRL有可能不存在subRL->_parent = parent;Node* pParent = parent;subR->_left = parent;parent->_parent = subR;//三叉链的链接会复杂一点if (parent == _root)//判断parent是否为根{//更新根节点_root = subR;_root->_parent = nullptr;}else{subR->_parent = pParent;if (parent == pParent->_left){pParent->_left = subR;}else{pParent->_right = subR;}}}void RotateR(Node* parent){//右单旋Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;Node* pParent = parent->_parent;subL->_right = parent;parent->_parent = subL;//三叉链的链接会复杂一点if (parent == _root){//更新根节点_root = subL;_root->_parent = nullptr;}else{subL->_parent = pParent;if (parent == pParent->_left){pParent->_left = subL;}else{pParent->_right = subL;}}}
3.3红黑色的验证
红黑树的检测分为两步:
1.检测其是否满足二叉搜索树(中序遍历是否有序)
2.检测其是否满足红黑树的性质
void _Inorder(Node* root){if (root == nullptr){return;}_Inorder(root->_left);cout << root->_data << endl;_Inorder(root->_right);}void Inorder(){if(_root){_Inorder(_root);}}
Node* GetRoot()//获取根节点{return _root;}bool IsValidRBTree(){Node* root = GetRoot();if (root == nullptr){return true;//空树也是红黑树}if (_root->_col != BLACK){cout << "违反性质2" << endl;return false;}//获取任何一条路径上的黑色节点数Node* cur = root;size_t blackSize = 0;while (cur){if (cur->_col == BLACK){++blackSize;}cur = cur->_right;}size_t k = 0;return _IsValidRBTree(root, k, blackSize);}bool _IsValidRBTree(Node* pRoot, size_t k, size_t blackSize){//走到空的时候判断k和black和是否相等if (pRoot == nullptr){if (k != blackSize){cout << "违反性质4" << endl;cout << k << " " << blackSize << endl;return false;}return true;}if (BLACK == pRoot->_col){++k;}//检测当前节点与其双亲节点是否为红色Node* parent = pRoot->_parent;if (parent && parent->_col == RED && pRoot->_col == RED){cout << "违反性质3:没有连在一起的红节点" << endl;return false;}return _IsValidRBTree(pRoot->_left, k, blackSize) && _IsValidRBTree(pRoot->_right, k, blackSize);}
3.4红黑树的查找
红黑树的查找和搜索树的查找一样只需要按照搜索树的规则进行查找就可以了。
Node* Find(const K& key){if (_root == nullptr){return _root;}Node* cur = _root;while (cur){if (cur->_data == key){break;}else if (key > cur->_data){//去右边找cur = cur->_right;}else{//去左边找cur = cur->_left;}}//找不到返回cur,cur此时走到nullptrreturn cur;}
3.5红黑树的删除
3.5.1删除详解
为什么要将红黑树的删除放在最后呢,这是有原因的,如果将红黑树的删除放前面那么十分钟肯定就学不会了。
因为红黑树的删除是有一定的难度的,所以要做好心理准备。
首先和插入一样,因为红黑树是平衡二叉树所以它的删除和二叉搜索树的删除是有些一样的地方的,如果不了二叉搜索树的删除可以参考:二叉搜索树
和插入一样,首先删除也分为这样的几步:
第一步:查找要删除的值data是否在红黑树中。
第二步:如果在要删除值在红黑树中 ,就要判断这个值是位于红黑树的哪个位置,因为在搜索树中不同位置的值的处理是不同的。
第三步:进行调整。
这样看是不是觉得红黑树的删除挺简单的呢,至少看起来是不难的!
接下来让我们先详解第二步,我们就将要删除的值称为del,(这样方便写,也方便看) del有以下几种情况:
情况1:del所在的节点左右子树都存在,那么这时候就需要寻找替代节点将,删除替代节点就行了。
情况2:del的左子树或者右子树是空的。
情况3:del是叶子节点。
接下来我们就对这几种情况作具体的分析。
情况1详解:如何寻找替代节点呢,其实如果了解搜索树的删除的朋友应该知道,无非就是去找del的前驱或者后继节点,然后用del的前驱或者后继节点的值,覆盖del就行了,那么下面要介绍两个概念,前驱节点和后继节点。
前驱节点:就是比当前节点值小的节点中值最大的节点,也就是它左子树中的最右节点。
后继节点:就是比当前节点值大的节点中值最小的节点,也就是它右子树中的最左节点。
寻找替代节点的详解图:
这时候就将情况1转化为情况2或者情况3了。因为替代节点就两种情况 ,要么是一个子树为空的节点(情况2),要么是叶子节点(情况3)。
情况2详解:
如果左右子树中有一个为空,另一个不为空。
如果左子树为空,右子树不为空,那么右子树肯定是红色节点,因为如果右子树为黑色的节点肯定会违背红黑树的性质:每条路径上都有相同路径的黑色节点。
右子树为空也是一样的,它左边的节点肯定是红色的节点,只需要将它不为空的节点作为替代节点,对替代节点进行操作就行了。
图示:
实际上这是将情况2转化为情况3。
情况3详解
如果叶子节点是黑色的是不能直接删除的,需要进行处理。 也就是第三步进行调整。
如果叶子节点是红色的,还需要进行处理吗?不需要的直接删除就行了 。
第三步调整详解
看了这么多了,怎么样是不是感觉红黑树其实也是比较简单的呢,不过接下来的调整也要分为好几种情况,你可别把自己绕晕了。
调整大概可以分成三种情况,为了接下来好描述,我将当前需要调整的节点定义为delNode,简称D。它的兄弟定义为brother,简称B,它的双亲定义为parent,简称P。 它兄弟的左孩子简称为BL,它兄弟的右孩子简称为BR,怎么样这么一堆名称是不是已经让你晕了,其实记首字母就行了,在代码实现里面没有用缩写应该很容易看懂。
如图: 情况1:
这个节点是根节点,将根节点变黑就行了。
如图:
情况2:
B是黑色的。
这时候又要分成两种情况了。B的孩子存在(BL或者BR),或者B的孩子不存在。
B的孩子存在:
如果B的孩子存在,要根据B的孩子和B的位置以及B的位置和P的位置的相对关系来判断。分为四种情况:
情况1:B在P的左边,如果BL存在,此时在P的位置进行左旋:然后变色,将B变成P的颜色将P变为黑色,BL变成黑色。因为其实这种情况下P的颜色是不确定的。
情况2:B在P的左边,如果BR存在,此时要进行两次旋转,在B的位置进行左旋,然后在P的位置进行右旋,将BR变为P的颜色,将P变为黑色。
情况3:B在P的右边,如果BR存在,此时在P的位置进行左旋,然后将B的颜色变成P的颜色,将P和BR的颜色变成黑色。
情况4:B在P的右边,如果BL存在,此时要进行两次旋转。在B的位置右旋,在P的位置左旋,将BL变为P的颜色,将P变为黑色。
B的孩子不存在:
理论上这种是最复杂的因为B的孩子不存在,B为黑色,D也为黑色,那么此时P就有两种情况,P为红色和P为黑色。
如果P为红色,那么此时将B变为红色,P变为黑色就结束了。
如图:
如果P为黑色,就需要以P为调整节点继续向上进行调整。我们需要将B和D都变为红色然后以P为调整节点,继续判断是属于哪种情况再进行调整。如图:
注意:这里要小心一种特殊情况,那就是P是根节点(_root),只需要将B变成红色结束即可。
看着很复杂但是写成代码却是最简单的,这里卖个关子,我想你应该可以猜到是为什么。哈哈哈哈。
情况3:
B是红色的,那么必然存在BL和BR且都是黑色的。(为什么,根据红黑树的形状得来的后面如果不太理解,看图你就知道了)
这时候就需要判断B和P的关系了,如果B在P的左边,进行右旋就行了,如果B在P的右边进行左旋就行了,旋转完需要变色。(或者先变色再旋转也可以)变色情况具体看图分析:
如图:
3.5.2删除的代码实现
这里只贴了删除部分的代码,如果有不理解的地方可以参看注释,其实代码还可以简化一些的,但是那样子代码的可读性会降低很多,所以就没有那么写,好的代码不是越精简越好,可读性,可维护性等都很重要。
//红黑树的删除,比插入还要难一些
//主要分为下面的几种情况
bool Erase(const K& data)
{//情况1:红黑树中不存在要删除的节点,返回false//情况2:只剩下根节点,删除根节点将根节点置空//情况3:要删除的节点左右子树都存在//情况4:要删除的节点只有左子树或者右子树//情况5:删除的是叶子节点Node* delNode = Find(data);//查找要删除的节点if (delNode == nullptr){//情况1:红黑树中不存在要删除的节点,返回falsecout << "节点不存在" << endl;return false;//这里判断红黑树中是否存在这个节点}if (_root == delNode&& _root->_left == nullptr&& _root->_right == nullptr){//情况2:只剩下根节点,删除根节点将根节点置空delete _root;_root = nullptr;return true;}if (delNode->_left && delNode->_right){//第三种情况是它的左右子树都不为空,那么这时候我们需要找替代节点,//然后将第三种情况转化为第四种或者第五种情况Node* replace = getReplaceNode(delNode);delNode->_data = replace->_data;delNode = replace;}if ((delNode->_left && delNode->_right == nullptr)|| (delNode->_left == nullptr && delNode->_right)){//第四种情况删除的这个节点有一个子节点为空另一个子节点不为空,//那么要删除的当前节点一定是黑色的节点,并且它的不为空的节点一定是红色节点,//否则的话将不满足红黑树的性质//将第四种情况转化为第五种情况Node* replace = delNode->_left != nullptr ? delNode->_left : delNode->_right;delNode->_data = replace->_data;delNode = replace;}if (delNode->_left == nullptr&& delNode->_right == nullptr){//走到这里说明删除的这个节点是叶子节点,如果叶子节点是红色的那么直接删除就好了,//但是叶子节点是黑色的话就需要进行旋转处理了if (delNode->_col == BLACK){//对黑色叶子节点进行处理AdjustDown(delNode);//调用函数进行处理}//删除节点Node* parent = delNode->_parent;if (parent->_left == delNode){parent->_left = nullptr;}else{parent->_right = nullptr;}delNode->_parent = nullptr;delete delNode;delNode = nullptr;}return true;
}//在删除的节点是叶子节点且节点是红色的情况下的调整逻辑
void AdjustDown(Node* delNode)
{//分成三种大的情况://情况1:如果这个节点是根节点,将它变黑返回就行了//情况2:它的兄弟节点为黑色//情况3:它的兄弟节点为红色Node* parent = delNode->_parent;Node* brother = (delNode == parent->_left ? parent->_right : parent->_right);if (_root == delNode){//情况1:如果这个节点是根节点,将它变黑返回就行了_root->_col = BLACK;return;}else if (brother && brother->_col == BLACK){//情况2:它的兄弟节点为黑色//这个又要分为两种情况//情况1:brother不存在左右孩子//情况2:brother存在左孩子或者右孩子,这时候brother的孩子肯定是红节点if (brother->_left == nullptr&& brother->_right == nullptr)//情况1:brother不存在左右孩子{//这时候又要分成两种情况//情况1:parent为红色,这时候只需要将brother变为红色,parent变为黑色即可//情况2:parent为黑色,这时候就需要以parent为调整节点继续向上进行调整if (parent->_col == RED){parent->_col = BLACK;brother->_col = RED;}else//情况2:parent为黑色,这时候就需要以parent为调整节点继续向上进行调整{if (parent == _root && brother->_col == BLACK){//说明此时只有三个节点且全部为黑//将brother变红然后返回brother->_col = RED;return;}else{//需要将brother变为红色,//以parent为调整节点继续调整brother->_col = RED;AdjustDown(parent);}}}else//情况2:brother存在左孩子或者右孩子,这时候brother的孩子肯定是红节点{//这时候因为brother是红色的,delNode也是红色的,且brother存在孩子//那么这时候就有四种情况://情况1:brother是parent的左孩子,并且brother存在左孩子//情况2:brother是parent的左孩子,并且brother存在右孩子//情况3:brother是parent的右孩子,并且brother存在右孩子//情况4:brother是parent的右孩子,且brother存在右孩子if (brother == parent->_left && brother->_left){//情况1:brother是parent的左孩子,并且brother存在左孩子Node* brotherLeft = brother->_left;if (brotherLeft){//在parent的位置右旋//brother的颜色变为parent的颜色//brotherLeft和parent都变为黑色brother->_col = parent->_col;parent->_col = brotherLeft->_col = BLACK;RotateR(parent);}else if (brother->_right) // 情况2:brother是parent的左孩子,并且brother存在右孩子{Node* brotherRight = brother->_right;//这时候要进行双旋,先左旋再右旋,在进行变色//在brother的位置进行左旋,然后在parent的位置进行右旋//brotherRight变成parent的颜色,parent变成黑色brotherRight->_col = parent->_col;parent->_col = BLACK;RotateL(brother);RotateR(parent);}}else if (brother == parent->_right && brother->_right){//情况3:brother是parent的右孩子,并且brother存在右孩子Node* brotherRight = brother->_right;if (brotherRight){//brother是parent的右孩子,且brother存在右孩子,进行左旋在parent的位置//brother的颜色变为parent的颜色,//parent和brotherRight变为黑色brother->_col = parent->_col;parent->_col = brotherRight->_col = BLACK;RotateL(parent);}else if (brother->_left)//情况4:brother是parent的右孩子,且brother存在左孩子{Node* britherLeft = brother->_left;//brother是parent的右孩子,且brother存在左孩子,//先在brither处右旋,再在parent处左旋//变色:parent变黑,brotherLeft变为parent的颜色britherLeft->_col = parent->_col;parent->_col = BLACK;RotateR(brother);RotateL(parent);}}}}else if (brother && brother->_col == RED){//情况3:它的兄弟节点为红色//如果brother是红色的,那么它肯定存在两个孩子并且两个孩子都是黑色的,//不然就不符合红黑树的性质// 这时候又分成两种情况://如果brother在parent的左边就右旋,然后将brother变黑,brother的right变红//如果brother在parent的右边就右旋,然后brother变黑,brother的left变红if (brother == parent->_left){Node* brotherRight = brother->_right;brotherRight->_col = RED;brother->_col = BLACK;RotateR(parent);//右旋}else{Node* brotherLeft = brother->_left;brotherLeft->_col = RED;brother->_col = BLACK;RotateL(parent);}}
}
//找一个节点的前驱
Node* prevNode(Node* root)
{Node* cur = root->_left;//前驱是左子树中的最大节点,也就是左子树中的最右节点while (cur && cur->_right){cur = cur->_right;}return cur;
}
//找一个节点的后继
Node* successNode(Node* root)
{Node* cur = root->_right;//后继是右子树中的最小节点,也就是右子树中的最左子树while (cur && cur->_left){cur = cur->_left;}return cur;
}
//找替代节点
//替代节点可以选前驱节点中符合条件的,如果前驱节点不符合条件就返回后继节点
Node* getReplaceNode(Node* delNode)
{Node* replace = prevNode(delNode);if (replace->_col == RED){//如果替代节点是红节点直接返回就可以了,这种情况说明这个找到的替代节点是叶子节点return replace;}else if (replace->_left)//如果这个替代节点是黑色的,但是它的左节点存在,//那么左节点一定是红色的,因为我们判断的是这棵子树的最右节点,//如果左孩子是黑色的,就不满足红黑色,每条路径黑节点的数量相等这个属性{return replace->_left;}return successNode(delNode);
}
到这里红黑树的删除就结束了,不知道你用了多久看完它呢, 不知道看完它你是哪种状态呢,是大彻大悟还是模模糊糊呢,亦或者是感觉脑子有点痒痒的~
3.6测试代码
测试其实没有什么好说的。你就找一堆数据进行插入并且删除就好了,最好是足够多的数据这样可以查出一写影藏的BUG,这种树状的结构查看起来比较麻烦,所以我写了一个数层次结构的遍历,这样遇到问题的时候打印一下,找个地方画画看看是哪里出问题了就比较好分析,查看内存的话,能把你绕晕,这也是一种偷懒的做法吧,当然也有一些小工具可以进行测试,但是太麻烦了,懒得搞。
//这里写一个红黑树的层次遍历,有bug调试起来真的太恶心了void Traversal(){_Traversal(_root);}void _Traversal(Node* root){if (root == nullptr){return;}//借助队列queue<Node*> q;q.push(root);while (!q.empty()){Node* cur = q.front();//取队头的数据cout << cur->_data;Color(cur->_col);cout << "左孩子:";Data(cur->_left);cout << "右孩子:"; Data(cur->_right);cout << "父亲:"; Data(cur->_parent);cout << endl;q.pop();if (cur->_left){//将左右子树入队q.push(cur->_left);}if (cur->_right){//将右子树入队q.push(cur->_right);}}}void Data(Node* cur){if (cur){cout << cur->_data <<" ";}else{cout << "NULL" <<" ";}}//将枚举类型打印为颜色void Color(enum Colour&col){if (col == BLACK){cout << "黑色"<<" ";}else{cout << "红色" <<" ";}}
void TestRBTreeErase()
{qyy::RBTree<int> rb1;int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14, 20, 30 };for (auto e : a){rb1.Insert(e);cout << "插入" << e << "->" << endl;}rb1.Traversal();rb1.Inorder();cout << rb1.IsValidRBTree() << endl;for (auto e : a){rb1.Erase(e);cout << "删除" << e << "->" << endl;}rb1.Inorder();}
3.7全部代码
// RBTree.hpp
#include<iostream>
#include<queue>
using namespace std;
namespace qyy
{//用来表示红黑色节点的颜色enum Colour{RED,BLACK,};template<class K>struct RBTreeNode{RBTreeNode<K>* _left;RBTreeNode<K>* _right;RBTreeNode<K>* _parent;K _data;Colour _col;//构造函数完成对节点的初始化RBTreeNode(const K& data):_left(nullptr),_right(nullptr),_parent(nullptr),_data(data),_col(RED){}};template<class K>class RBTree{public:typedef RBTreeNode<K> Node;RBTree():_root(nullptr){}bool Insert(const K& data){//如果根节点为空直接插入并初始化根节点if (_root == nullptr){_root = new Node(data);_root->_col = BLACK;//将根节点变黑}Node* cur = _root;Node* parent = nullptr;while (cur){//新插入的节点都是红色的,if (cur->_data == data){//节点中存在data不需要进行插入return false;}else if (data > cur->_data){parent = cur;cur = cur->_right;}else if (data < cur->_data){parent = cur;cur = cur->_left;}}//申请新节点进行插入cur = new Node(data);cur->_parent = parent;cur->_col = RED;if (cur->_data > parent->_data){parent->_right = cur;}else{parent->_left = cur;}//插入之后进行调整while (parent && parent->_col == RED)//插入cur之后只有当parent存在且为红色才需要处理{//如果parent存在且为红色,那么说明它的parent的父亲一定存在,且为黑色Node* grandfather = parent->_parent;//这时候需要根据uncle的颜色进行判断来区分是哪种情况然后再进行处理if (parent == grandfather->_left){Node* uncle = grandfather->_right;if (uncle && uncle->_col == RED){//如果舅舅存在且为红,需要将parent和uncle都变为黑色,然后再将grandfather变为红色//继续迭代处理看是属于哪种情况uncle->_col = parent->_col = BLACK;grandfather->_col = RED;//迭代cur = grandfather;parent = cur->_parent;}else{//如果uncle不存在或者uncle存在且为黑色,就要进行其它的处理了if (cur == parent->_right)//parent == grandfather->_left{//这时候需要进行双旋//先进行一次左旋RotateL(parent);//在parent位置::swap(cur, parent);//交换两个指针//通过上面的处理可以将左右双旋变为右单旋}//这里处理的就是右单旋的情况,RotateR(grandfather);//变色parent->_col = BLACK;grandfather->_col = RED;break;}}else//parent == grandfather->_right{Node* uncle = grandfather->_left;//这里也是一样的,分为uncle为红色和不为红色两种情况if (uncle && uncle->_col == RED){//如果uncle为红色,就要将parent和uncle都变为黑色,将grandfather变为红色//然后迭代进行其它处理parent->_col = uncle->_col = BLACK;grandfather->_col = RED;//迭代cur = grandfather;parent = cur->_parent;}else{//这里处理的是uncle为黑色或者uncle不存在的情况//需要旋转+变色进行处理if (cur == parent->_left)//parent == grandfather->_right{//右单旋RotateR(parent);::swap(cur, parent);//这样处理就可以将需要双旋的场景转化为单旋}RotateL(grandfather);//变色parent->_col = BLACK;grandfather->_col = RED;break;}}}_root->_col = BLACK;//不管怎么样最后将根节点变为黑色return true;}//这里写一个红黑树的层次遍历,有bug调试起来真的太恶心了void Traversal(){_Traversal(_root);}void _Traversal(Node* root){if (root == nullptr){return;}//借助队列queue<Node*> q;q.push(root);while (!q.empty()){Node* cur = q.front();//取队头的数据cout << cur->_data;Color(cur->_col);cout << "左孩子:";Data(cur->_left);cout << "右孩子:"; Data(cur->_right);cout << "父亲:"; Data(cur->_parent);cout << endl;q.pop();if (cur->_left){//将左右子树入队q.push(cur->_left);}if (cur->_right){//将右子树入队q.push(cur->_right);}}}void Data(Node* cur){if (cur){cout << cur->_data <<" ";}else{cout << "NULL" <<" ";}}//将枚举类型打印为颜色void Color(enum Colour&col){if (col == BLACK){cout << "黑色"<<" ";}else{cout << "红色" <<" ";}}//左右单旋的代码void RotateL(Node*parent){//左单旋Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)//subRL有可能不存在subRL->_parent = parent;Node* pParent = parent->_parent;//这里之前写错了,刚开始没事,但是插入到后面就会出错subR->_left = parent;parent->_parent = subR;//三叉链的链接会复杂一点if (parent == _root)//判断parent是否为根{//更新根节点_root = subR;_root->_parent = nullptr;}else{subR->_parent = pParent;if (parent == pParent->_left){pParent->_left = subR;}else{pParent->_right = subR;}}}void RotateR(Node* parent){//右单旋Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;Node* pParent = parent->_parent;subL->_right = parent;parent->_parent = subL;//三叉链的链接会复杂一点if (parent == _root){//更新根节点_root = subL;_root->_parent = nullptr;}else{subL->_parent = pParent;if (parent == pParent->_left){pParent->_left = subL;}else{pParent->_right = subL;}}}//红黑树的删除,比插入还要难一些//主要分为下面的几种情况bool Erase(const K& data){//情况1:红黑树中不存在要删除的节点,返回false//情况2:只剩下根节点,删除根节点将根节点置空//情况3:要删除的节点左右子树都存在//情况4:要删除的节点只有左子树或者右子树//情况5:删除的是叶子节点Node* delNode = Find(data);//查找要删除的节点if (delNode == nullptr){//情况1:红黑树中不存在要删除的节点,返回falsecout << "节点不存在" << endl;return false;//这里判断红黑树中是否存在这个节点}if (_root == delNode && _root->_left == nullptr&& _root->_right == nullptr){//情况2:只剩下根节点,删除根节点将根节点置空delete _root;_root = nullptr;return true;}if (delNode->_left && delNode->_right){//第三种情况是它的左右子树都不为空,那么这时候我们需要找替代节点,//然后将第三种情况转化为第四种或者第五种情况Node* replace = getReplaceNode(delNode);delNode->_data = replace->_data;delNode = replace;}if ((delNode->_left && delNode->_right == nullptr)|| (delNode->_left == nullptr && delNode->_right)){//第四种情况删除的这个节点有一个子节点为空另一个子节点不为空,//那么要删除的当前节点一定是黑色的节点,并且它的不为空的节点一定是红色节点,//否则的话将不满足红黑树的性质//将第四种情况转化为第五种情况Node* replace = delNode->_left != nullptr ? delNode->_left : delNode->_right;delNode->_data = replace->_data;delNode = replace;}if(delNode->_left == nullptr&&delNode->_right == nullptr){//走到这里说明删除的这个节点是叶子节点,如果叶子节点是红色的那么直接删除就好了,//但是叶子节点是黑色的话就需要进行旋转处理了if (delNode->_col == BLACK){//对黑色叶子节点进行处理AdjustDown(delNode);//调用函数进行处理}//删除节点Node* parent = delNode->_parent;if (parent->_left == delNode){parent->_left = nullptr;}else{parent->_right = nullptr;}delNode->_parent = nullptr;delete delNode;delNode = nullptr; }return true;}//在删除的节点是叶子节点且节点是红色的情况下的调整逻辑void AdjustDown(Node* delNode){//分成三种大的情况://情况1:如果这个节点是根节点,将它变黑返回就行了//情况2:它的兄弟节点为黑色//情况3:它的兄弟节点为红色Node* parent = delNode->_parent;Node* brother = (delNode == parent->_left ? parent->_right : parent->_right);if (_root == delNode){//情况1:如果这个节点是根节点,将它变黑返回就行了_root->_col = BLACK;return;}else if (brother && brother->_col == BLACK){//情况2:它的兄弟节点为黑色//这个又要分为两种情况//情况1:brother不存在左右孩子//情况2:brother存在左孩子或者右孩子,这时候brother的孩子肯定是红节点if (brother->_left == nullptr&& brother->_right == nullptr)//情况1:brother不存在左右孩子{//这时候又要分成两种情况//情况1:parent为红色,这时候只需要将brother变为红色,parent变为黑色即可//情况2:parent为黑色,这时候就需要以parent为调整节点继续向上进行调整if (parent->_col == RED){parent->_col = BLACK;brother->_col = RED;}else//情况2:parent为黑色,这时候就需要以parent为调整节点继续向上进行调整{if (parent == _root && brother->_col == BLACK){//说明此时只有三个节点且全部为黑//将brother变红然后返回brother->_col = RED;return;}else{//需要将brother变为红色,//以parent为调整节点继续调整brother->_col = RED;AdjustDown(parent);}}}else//情况2:brother存在左孩子或者右孩子,这时候brother的孩子肯定是红节点{//这时候因为brother是红色的,delNode也是红色的,且brother存在孩子//那么这时候就有四种情况://情况1:brother是parent的左孩子,并且brother存在左孩子//情况2:brother是parent的左孩子,并且brother存在右孩子//情况3:brother是parent的右孩子,并且brother存在右孩子//情况4:brother是parent的右孩子,且brother存在右孩子if (brother == parent->_left && brother->_left){//情况1:brother是parent的左孩子,并且brother存在左孩子Node* brotherLeft = brother->_left;if(brotherLeft){//在parent的位置右旋//brother的颜色变为parent的颜色//brotherLeft和parent都变为黑色brother->_col = parent->_col;parent->_col = brotherLeft->_col = BLACK;RotateR(parent);}else if (brother->_right) // 情况2:brother是parent的左孩子,并且brother存在右孩子{Node* brotherRight = brother->_right;//这时候要进行双旋,先左旋再右旋,在进行变色//在brother的位置进行左旋,然后在parent的位置进行右旋//brotherRight变成parent的颜色,parent变成黑色brotherRight->_col = parent->_col;parent->_col = BLACK;RotateL(brother);RotateR(parent);}}else if(brother == parent->_right && brother->_right){//情况3:brother是parent的右孩子,并且brother存在右孩子Node* brotherRight = brother->_right;if(brotherRight){//brother是parent的右孩子,且brother存在右孩子,进行左旋在parent的位置//brother的颜色变为parent的颜色,//parent和brotherRight变为黑色brother->_col = parent->_col;parent->_col = brotherRight->_col = BLACK;RotateL(parent);}else if (brother->_left)//情况4:brother是parent的右孩子,且brother存在左孩子{Node* britherLeft = brother->_left;//brother是parent的右孩子,且brother存在左孩子,//先在brither处右旋,再在parent处左旋//变色:parent变黑,brotherLeft变为parent的颜色britherLeft->_col = parent->_col;parent->_col = BLACK;RotateR(brother);RotateL(parent);}}}}else if (brother && brother->_col == RED){//情况3:它的兄弟节点为红色//如果brother是红色的,那么它肯定存在两个孩子并且两个孩子都是黑色的,//不然就不符合红黑树的性质// 这时候又分成两种情况://如果brother在parent的左边就右旋,然后将brother变黑,brother的right变红//如果brother在parent的右边就右旋,然后brother变黑,brother的left变红if (brother == parent->_left){Node* brotherRight = brother->_right;brotherRight->_col = RED;brother->_col = BLACK;RotateR(parent);//右旋}else{Node* brotherLeft = brother->_left;brotherLeft->_col = RED;brother->_col = BLACK;RotateL(parent);}}}//找一个节点的前驱Node* prevNode(Node* root){Node* cur = root->_left;//前驱是左子树中的最大节点,也就是左子树中的最右节点while (cur && cur->_right){cur = cur->_right;}return cur;}//找一个节点的后继Node* successNode(Node* root){Node* cur = root->_right;//后继是右子树中的最小节点,也就是右子树中的最左子树while (cur && cur->_left){cur = cur->_left;}return cur;}//找替代节点//替代节点可以选前驱节点中符合条件的,如果前驱节点不符合条件就返回后继节点Node* getReplaceNode(Node* delNode){Node* replace = prevNode(delNode);if (replace->_col == RED){//如果替代节点是红节点直接返回就可以了,这种情况说明这个找到的替代节点是叶子节点return replace;}else if(replace->_left)//如果这个替代节点是黑色的,但是它的左节点存在,//那么左节点一定是红色的,因为我们判断的是这棵子树的最右节点,//如果左孩子是黑色的,就不满足红黑色,每条路径黑节点的数量相等这个属性{return replace->_left;}return successNode(delNode);}void _Inorder(Node* root){if (root == nullptr){return;}_Inorder(root->_left);cout << root->_data << endl;_Inorder(root->_right);}void Inorder(){_Inorder(_root);}Node* Find(const K& key){if (_root == nullptr){return _root;}Node* cur = _root;while (cur){if (cur->_data == key){break;}else if (key > cur->_data){//去右边找cur = cur->_right;}else{//去左边找cur = cur->_left;}}//找不到返回cur,cur此时走到nullptrreturn cur;}Node* GetRoot()//获取根节点{return _root;}bool IsValidRBTree(){Node* root = GetRoot();if (root == nullptr){return true;//空树也是红黑树}if (_root->_col != BLACK){cout << "违反性质2" << endl;return false;}//获取任何一条路径上的黑色节点数Node* cur = root;size_t blackSize = 0;while (cur){if (cur->_col == BLACK){++blackSize;}cur = cur->_right;}size_t k = 0;return _IsValidRBTree(root, k, blackSize);}bool _IsValidRBTree(Node* pRoot, size_t k, size_t blackSize){//走到空的时候判断k和black和是否相等if (pRoot == nullptr){if (k != blackSize){cout << "违反性质4" << endl;cout << k << " " << blackSize << endl;return false;}return true;}if (BLACK == pRoot->_col){++k;}//检测当前节点与其双亲节点是否为红色Node* parent = pRoot->_parent;if (parent && parent->_col == RED && pRoot->_col == RED){cout << "违反性质3:没有连在一起的红节点" << endl;return false;}return _IsValidRBTree(pRoot->_left, k, blackSize) && _IsValidRBTree(pRoot->_right, k, blackSize);}private:Node* _root;};
}
4.红黑树与AVL树的比较
红黑树和AVL树都是高度平衡的二叉搜索树,增删查改的时间复杂度都是O(logN),红黑色不追求绝对的平衡,其只需要保证最长路径不超过最短路径的两倍即可,相对而言,红黑树降低了插入和删除时旋转的次数,所以在经常需要增删的的结构中比AVL树更优,而且红黑树的实现较为简单,所以在实际中红黑树用的更多一些。
5.红黑树的应用
红黑树作为一种高效搜索的数据结构,在很多地方都有它的运用,比如在C++STL库里面的map/set,mutil_map/mutil_set。其他语言比如Java的库里面也用了红黑树,比如著名的开源操作系统linux中也用到了红黑树等等...