目录
红黑树简介
红黑树框架构建
构造函数 析构函数
四种旋转逻辑(左单旋,右单旋,左右双旋,右左双旋)
左右双旋与右左双旋
左单旋
右单旋
红黑树的插入 Insert
插入节点的颜色
红黑树插入的三种情况
代码编写
插入完整代码
红黑树的删除 Erase
1. 当删除节点有两个孩子
2. 当删除节点有一个孩子
3. 当删除节点没有孩子(叶子节点)
3.1 该节点为红色节点
3.2 该节点为黑色节点
结语
红黑树简介
首先我们来聊一聊红黑树是什么
在之前的学习中,我们了解到了二叉搜索树,AVL树
二叉搜索树的特性是左子树 < 根 < 右子树,但是二叉搜索树也有缺点,就是如果处在极端情况下,那么二叉搜索树的效率就和链表差不多
为了解决这个问题,我们学习了AVL树,这棵树是通过旋转来控制树的高度,以此来达到绝对平衡的目的,也就是任何一棵子树,左右高度差都不会超过1
但是这也伴随着一个问题就是,如果我们每一次插入,都判断、旋转,这样子的效率固然是高,但是有没有更好的办法,让其在保持高度平衡的同时,尽量减少旋转的次数
这时候我们就引出了我们的红黑树
AVL树是通过平衡因子来控制树的高度的,而红黑树是一种通过颜色控制高度的树
红黑树有以下四条规则:
- 每个结点不是红色就是黑色
- 根节点是黑色的
- 如果一个节点是红色的,则它的两个孩子结点是黑色的
- 每条路径均包含相同数目的黑色结点
红色节点是不能与黑色节点相连的,并且,每条路径都有相同黑色节点,这点非常重要
我们来思考一下,为什么通过颜色,就能控制住树的高度,还有就是这样子是否真的能保证树的平衡
注意看,上图是最极限的情况(高度),因为两边都不能再加黑色节点了,每条路径都只有两个黑色节点,如果再加节点的话,在(根节点的)右子树加那么高度就没有差那么多了,不是最极限的情况,如果在(根节点的)左子树插入节点的话,就只能插入黑色的节点,但是单在左树上插入黑色节点,那么其他路径的黑色节点和这个的就不一样了,所以就需要旋转,这样就控制住了高度
而且,红黑树要求的不是绝对的平衡,而是一种相对平衡
他不像AVL树,一有不平衡就旋转,他会到真的不得已了才旋转,旋转次数下来了,效率就高了,并且红黑树还能保持树的高度平衡
所以,在库里面的set、map封装都是拿红黑树做的底层
红黑树框架构建
首先,我们的红黑树是通过颜色控制的,所以我们可以写一个联合体enum,我们后面就可以直接写对应颜色的名字,这样代码的可读性也会提高
enum Colour
{
RED,
BLACK
};
然后我们还需要写一个红黑树的节点类,因为一个节点里面要存指针,存联合体,存pair类型的数据,所以我们就只能写一个类,然后将这个类做为红黑树的成员变量
template<class k, class v>
struct RBTreeNode
{
RBTreeNode<k, v>* _left;// 左指针
RBTreeNode<k, v>* _right;// 右指针
RBTreeNode<k, v>* _parent;// 父节点
Colour _col;// 颜色
pair<k, v> _data;// 数据
RBTreeNode(const pair<k, v>& data)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _data(data)
{}
// 这里的颜色不需要初始化,我们后面插入节点的时候才初始化
};
最后,我们还需要将这个节点类做为红黑树的成员变量,当然我们在这之前还可以对其typedef一下,可以将其直接typedef成Node,这样我们也好写
然后红黑树这个类需要的是一个节点指针,这个指针指向根节点
template<class K, class V>
class RBTree
{
typedef RBTreeNode<K, V> Node;
public:
private:
Node* _root;
};
构造函数 析构函数
由于我们的成员变量是一个类,默认生成的构造函数会调用这个类的构造,所以我们并不需要显示写构造,所以写不写其实一样
但是考虑到有些人会喜欢加上拷贝构造,那我们就需要强制编译器生成一个构造函数
// 强制编译器生成构造
RBTree() = default;
至于析构的话,我们直接写一个递归即可
先递归左子树,然后是右子树,最后删除当前节点即可,至于递归出口就是,当该节点为空,就退出
当然我们可以写一个函数来完成这个递归的逻辑,最后在函数结束后,我们再将这个根节点置空
~RBTree()
{
Destroy(_root);
_root = nullptr;
}
void Destroy(Node* root)
{
if (root == nullptr)return;
Destroy(root->_left);
Destroy(root->_right);
delete root;
}
四种旋转逻辑(左单旋,右单旋,左右双旋,右左双旋)
左右双旋与右左双旋
我们在AVL树中,之所以要单独实现这个逻辑,是因为双旋之后的平衡因子需要我们另外去控制
但是我们在红黑树中并没有平衡因子这个概念,而且我们颜色的控制还是统一在旋转完之后控制的
所以我们并不需要显示实现双旋,要实现双旋只需要将把两个单旋直接拿来用即可
左单旋
左单旋的代码如下:(解析在代码下面)
//左单旋
void RotateL(Node* parent)
{
///
Node* curR = parent->_right;
Node* curRL = curR->_left;
Node* Parentparent = parent->_parent;
//旋转逻辑
parent->_right = curRL;
curR->_left = parent;
///
//处理parent
if (curRL)curRL->_parent = parent;
parent->_parent = curR;
///
if (!Parentparent)
{
_root = curR;
curR->_parent = nullptr;
}
else
{
if (Parentparent->_left == parent)
Parentparent->_left = curR;
else
Parentparent->_right = curR;
curR->_parent = Parentparent;
}
///
}
这代码其实很简单,不要将其想得太复杂,首先我们是先将三个节点定义了出来
其中第三个是parent的_parent,我们将其命名为Parentparent
然后将curRL变成parent的右节点
然后parent自己成为curR的左节点
因为本来就是curR那边多出来了,我们旋转一下之后,parent下去了,那么自然就平衡了
(上述为第一块代码区域)
而后面的代码就都是在干一件事:找父节点
因为我们的节点是有三个指针的,一个左,一个右,一个parent
我们接下来解决的就是parent指针的指向
首先是curRL,因为我们不确定其是否为空,所以我们需要判断一下,如果存在,再让其的parent指针指向parent
(上述为第二块代码区域)
然后是parent,parent的_parent指针指向的是curR
最后是curR,我们无法确定Parentparent是否为空,因为如果parent节点旋转之前是根节点呢
所以我们就加了一条判断逻辑(如果为空,就直接让curR做根节点,并将curR的parent节点置空)
然后,如果Parentparent不为空,因为Parentparent还指向着parent节点,所以我们就直接判断curR是要链接在Parentparent的左边还是右边
最后让curR的parent指针指向Parentparent
(上述为第三块代码区域)
右单旋
右单旋其实就是左单旋的镜像处理,代码如下:
// 右单旋
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR;
if (subLR)
subLR->_parent = parent;
Node* parentParent = parent->_parent;
subL->_right = parent;
parent->_parent = subL;
if (parentParent == nullptr)
{
_root = subL;
subL->_parent = nullptr;
}
else
{
if (parent == parentParent->_left)
{
parentParent->_left = subL;
}
else
{
parentParent->_right = subL;
}
subL->_parent = parentParent;
}
}
红黑树的插入 Insert
插入节点的颜色
- 每个结点不是红色就是黑色
- 根节点是黑色的
- 如果一个节点是红色的,则它的两个孩子结点是黑色的
- 每条路径均包含相同数目的黑色结点
这里我们需要明白的一个点是,我们插入的节点是要什么颜色的
颜色就分为红色和黑色
如果我们插入黑色呢?
只要我们插入黑色,那就必须要调整,因为规则 4 是:每条路径上的黑色节点数量都需要相同
但是我们插入了黑色节点之后,就破坏了规则 4 ,所以就必须要进行处理
但是如果我们插入的是一个红色节点
当我们为红色节点的时候,如果父节点为黑色,那么我们就可以直接插入,并不需要做任何调整,因为并没有违反任何一条规则
但是如果我们的父节点是红色,那就违反规则 3 了,这时候才需要做出相应的调整
我们试想一下,插入黑色节点,就必须要调整,但是如果我们插入红色节点的话,如果父节点也是红才需要调整
就像是你考差了,你拿给爸爸那就肯定要挨顿打,但是如果拿给妈妈看,说不定妈妈心情好,就不会打,说不定看你这么差,就打你一顿
既然选爸爸一定会,选妈妈可能会,那我们肯定还是选妈妈嘛,说不定呢
综上,我们插入的节点颜色为红色
红黑树插入的三种情况
- 每个结点不是红色就是黑色
- 根节点是黑色的
- 如果一个节点是红色的,则它的两个孩子结点是黑色的
- 每条路径均包含相同数目的黑色结点
由于我们插入的节点为红色,那么就只有当父节点为红色的时候,才会进入调整的逻辑,所以父节点肯定是红色的
根据规定 2,根节点是黑色的,那既然父节点为红,那上面就一定还有一个爷爷(grandpa)节点(黑色)
那现在就是爷爷节点为黑,父节点和新插入节点为红,就只有一个叔叔(uncle)节点是不确定的
目前情况如上
而叔叔节点分三种情况:为红色、为黑、不存在
1. 叔叔节点为红
如果叔叔节点为红色的话,那就意味着,我们是不需要旋转控制的,因为如果叔叔为黑,父亲和新插入节点都为红,那么父亲和新节点下面就必然有黑色节点,所以新插入的节点其实并不是新插入的,而是其他情况变过来的
但是如果叔叔也为红,那就不会有这种情况,我们只需要变色处理即可
具体就是将父节点和叔叔节点变黑,然后新插入节点还是红色,爷爷节点变红即可
2. 叔叔节点不存在
如果叔叔节点不存在的话,那我们就必须要旋转了
这种情况就是旋转 + 变色
3. 叔叔节点为黑
叔叔节点为黑的话,那也是必须要旋转的
而且,这种情况一定是上面的情况变过来的,因为如果叔叔为黑,那么父节点和新插入节点为红,就不满足每条路径都只有一个黑色节点的规则,所以,父节点的另一个孩子节点一定为黑,并且新插入节点也一定有两个孩子节点且为黑
但是这时候新插入节点和父节点都为红色啊,所以就一定是其他情况变过来的
代码编写
关于红黑树的插入,我们在插入之前,需要先判断一下根节点是否为空,如果为空,就证明这是一棵空树,我们直接new一个节点给根,然后退出即可
接着需要查找一下这个值在不在红黑树里面,如果在的话,我们就没有插入的必要了
bool Insert(const pair<K, V>& data)
{
if (_root == nullptr)
{
_root = new Node(data);
_root->_col = BLACK;
return true;
}
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
if (cur->_data.first < data.first)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_data.first > data.first)
{
parent = cur;
cur = cur->_left;
}
else
return false;
}
如果能走到下面的逻辑,就证明我们已经找到待插入的位置了,我们只需要做三件事:
- new一个新节点
- 将这个节点的_parent指针与parent的孩子指针处理好
- 新节点颜色设置为红色
cur = new Node(data);
cur->_col = RED;
if (cur->_data.first < parent->_data.first)
parent->_left = cur;
else
parent->_right = cur;
cur->_parent = parent;
接着就是颜色改变与旋转的逻辑了
首先,我们的父节点为黑色的话,是不需要进入这个逻辑的,直接插入节点就好,不需要任何调整
然后当我们进入循环,代表着父节点一定为红
这时候就要开始分情况了,叔叔节点在爷爷节点的左边、叔叔节点在爷爷节点的右边
这是两种情况,代表着(右单旋、左右双旋)和(左单旋、右左双旋)
接着我们就是进入判断叔叔的环节了,我们先把代码实现一下:
while (parent && parent->_col == RED)
{
Node* grandpa = parent->_parent;
if (grandpa->_left == parent)
{
// parent 在爷爷节点左边
}
else
{
// parent 在爷爷节点右边
}
}
然后叔叔又分三种情况:
- 叔叔为红
- 叔叔不存在
- 叔叔为黑
这里的话,叔叔为黑与不存在可以当成是一种情况,因为这两种情况都是要旋转
并且不管叔叔在不在,最后变色都不需要叔叔来变色
我们看上图,两种情况都是爷爷变红,父节点变黑
而同时,也还有另一种情况,就是双旋的问题,这个其实比较简单,就是新插入节点在父节点的右或左的问题,如果父节点在爷爷的左,新插入节点在父亲的左,那就是右单旋
如果新插入节点在父亲的右,那就是左右双旋,父节点在爷爷右的情况镜像处理即可
这两个唯一的区别就在于:
双旋是新插入节点变黑,爷爷变红
单旋是父节点变黑,爷爷变红
代码如下:
while (parent && parent->_col == RED)
{
Node* grandpa = parent->_parent;
if (grandpa->_left == parent)
{
// parent 在爷爷节点左边
Node* uncle = grandpa->_right;
if (uncle && uncle->_col == RED)
{
uncle->_col = parent->_col = BLACK;
grandpa->_col = RED;
cur = grandpa;
parent = cur->_parent;
}
else
{
if (cur == parent->_left)
{
// 单旋的逻辑
RotateR(grandpa);
parent->_col = BLACK;
grandpa->_col = RED;
}
else
{
// 双旋的逻辑
RotateL(parent);
RotateR(grandpa);
cur->_col = BLACK;
grandpa->_col = RED;
}
break;
}
}
else
{
// parent 在爷爷节点右边
Node* uncle = grandpa->_left;
if (uncle && uncle->_col == RED)
{
grandpa->_col = RED;
uncle->_col = parent->_col = BLACK;
cur = grandpa;
parent = cur->_parent;
}
else
{
if (cur == parent->_right)
{
RotateL(grandpa);
grandpa->_col = RED;
parent->_col = BLACK;
}
else
{
RotateR(parent);
RotateL(grandpa);
grandpa->_col = RED;
cur->_col = BLACK;
}
break;
}
}
}
当我们颜色调节和旋转的逻辑处理好之后,我们只需要再让根节点变成黑色即可
因为我们在颜色调节过程中可能会将根节点调整成红色的,比如情况一
这时候我们以防万一,就需要加上这一步,最后return true即可
_root->_col = BLACK;
return true;
插入完整代码
bool Insert(const pair<K, V>& data)
{
if (_root == nullptr)
{
_root = new Node(data);
_root->_col = BLACK;
return true;
}
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
if (cur->_data.first < data.first)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_data.first > data.first)
{
parent = cur;
cur = cur->_left;
}
else
return false;
}
cur = new Node(data);
cur->_col = RED;
if (cur->_data.first < parent->_data.first)
parent->_left = cur;
else
parent->_right = cur;
cur->_parent = parent;
Node* newone = cur;// 提前记录值以便返回
while (parent && parent->_col == RED)
{
Node* grandpa = parent->_parent;
if (grandpa->_left == parent)
{
// parent 在爷爷节点左边
Node* uncle = grandpa->_right;
if (uncle && uncle->_col == RED)
{
uncle->_col = parent->_col = BLACK;
grandpa->_col = RED;
cur = grandpa;
parent = cur->_parent;
}
else
{
if (cur == parent->_left)
{
// 单旋的逻辑
RotateR(grandpa);
parent->_col = BLACK;
grandpa->_col = RED;
}
else
{
// 双旋的逻辑
RotateL(parent);
RotateR(grandpa);
cur->_col = BLACK;
grandpa->_col = RED;
}
break;
}
}
else
{
// parent 在爷爷节点右边
Node* uncle = grandpa->_left;
if (uncle && uncle->_col == RED)
{
grandpa->_col = RED;
uncle->_col = parent->_col = BLACK;
cur = grandpa;
parent = cur->_parent;
}
else
{
if (cur == parent->_right)
{
RotateL(grandpa);
grandpa->_col = RED;
parent->_col = BLACK;
}
else
{
RotateR(parent);
RotateL(grandpa);
grandpa->_col = RED;
cur->_col = BLACK;
}
break;
}
}
}
_root->_col = BLACK;
return true;
}
红黑树的删除 Erase
红黑树的删除相对来说会更为复杂,因为相比于插入的逻辑,删除会多出更多情况
1. 当删除节点有两个孩子
当删除的节点有两个孩子的时候,我们就需要转换问题:
首先我们需要找到左子树的最右节点或者右子树的最左节点,这里我们以右子树的最左节点为例
然后我们让右子树最左节点将待删除节点的数据覆盖,然后问题就转换成了删除右子树的最左节点
我们之所以要找右子树的最左,是因为这个节点最符合比待删除节点的左子树大,比待删除节点的右子树小,所以我们选择这个节点覆盖待删除节点的数据,然后删除右子树最左节点
而且由于红黑树的特殊性,右子树的最左节点只能是叶子节点,或者只有一个孩子节点且这个孩子节点为叶子节点
试想一下,已经是最左节点了,就不可能有左孩子,那就是右孩子可能在或可能不在
如果不在,那最左节点就是叶子节点,如果右右子树,那么最左节点只能是黑色且这个孩子节点只能是红色,因为如果孩子节点为黑色,那么左边那条路径的黑色节点数量为1,右边的为2,数量不相等,不符合规矩,所以是不可能存在的
综上,如果有一个孩子节点的话,那么这个孩子节点只能是红色的,如果孩子为红色的,那么父亲就一定是黑色的
所以,我们就将问题由删除一个有两个孩子节点的节点,转换成了删除一个有一个孩子或者没有孩子节点的节点,也就是转换成了我们下面讨论的两种大情况
2. 当删除节点有一个孩子
如上所述,如果删除节点只有一个孩子节点的话,那么孩子一定为红,父亲一定为黑
面对这种情况,我们只需要将孩子节点的值赋给父亲,然后变成删除孩子节点
并且,如果我们单单是删除一个红色叶子节点的话,是不需要做任何处理的,直接删除即可,因为删除一个红色节点,不会影响任何一条规则,高度差也不会变成非旋转不可的情况,所以直接删除即可
总结,如果待删除节点只有一个孩子的话,我们只需要将孩子节点的值赋值给父节点,然后将问题转换成删除孩子节点即可
3. 当删除节点没有孩子(叶子节点)
3.1 该节点为红色节点
这种情况直接将该节点删除即可,不需要做任何处理
3.2 该节点为黑色节点
既然这个节点为黑色,那么我们就需要去看这个兄弟节点,因为父节点是什么颜色并无所谓,在后面的逻辑里,父节点的颜色只需要和旋转到最上面位置的节点进行覆盖即可
3.2.1 兄弟节点为红色
既然兄弟为红色,那么父亲就一定为黑色,但是此时我们的待删除节点为黑色,这时,待删除节点路径就有两个黑色节点,兄弟的那条路径只有一个,那就意味着兄弟节点一定有两个孩子,且这两个孩子都为黑
面对这种情况,我们要做的只是旋转 + 变色,并且单旋足矣
代码如下(下图中的uncle就是上文中的uncle节点,这里写错了):
// uncle节点的颜色为红
if (parent->_left == uncle)
{
uncle->_col = BLACK;
uncle->_right->_col = RED;
RotateR(parent);
}
else
{
uncle->_col = BLACK;
uncle->_left->_col = RED;
RotateL(parent);
}
3.2.2 兄弟节点为黑色
如果是兄弟节点为黑色的情况,我们就需要讨论兄弟节点有没有孩子,因为待删除节点也是黑色的,所以删除之后路径上的黑色节点就少了一个,这时候如果兄弟节点有孩子节点的话,我们就能在旋转后通过染色来控制黑色节点的数量
并且由于待删除节点是叶子节点且为黑,所以兄弟节点的孩子必定为红,因为为黑的话路径上的黑色节点数量就不一样了
接下来要分兄弟节点有一个孩子,两个孩子,没有孩子三种情况讨论
3.2.2.1 兄弟节点有一个红色孩子节点
这时候我们就需要看,这个红色孩子节点在兄弟节点的左还是右了
如果兄弟节点在父节点的左,并且兄弟节点的孩子节点在兄弟节点的左,那么我们就右单旋+变色即可,但是这里的变色要注意,我们删除了节点 + 旋转之后,单从结果来看是兄弟节点在最上面,然后父节点下来了,那我们父节点的颜色是不知道的,所以我们只能让兄弟节点的颜色变成父节点的颜色,然后父节点和兄弟节点的孩子节点都变成黑色
如果兄弟节点(按上图)的孩子节点在右的话,就直接走双旋逻辑即可,只不过变色变成了兄弟节点的孩子节点变成父节点的颜色,然后兄弟节点和父节点变黑而已
3.2.2.2 兄弟节点有两个红色孩子节点
面对这种情况,直接单旋即可,变色的逻辑和上面只有一个子节点且同向的处理一样
3.2.2.3 兄弟节点没有孩子节点
如果没有孩子节点,那就意味着,我们在删除完节点之后,没有多余的节点变色使每条路径上的黑色节点与原来保持一致
这时候就得看父节点了,所以我们还得再分两种情况,一种是父节点为红,一种是父节点为黑
3.2.2.3.1 父节点为红
如果此时父节点为红的话,那么就是原本为每条路径都只有一个黑色节点,这时候删除了一个,要想保持原本的黑色节点的数量的话,这时我们可以将父节点染黑,然后兄弟节点变红即可,这样这棵子树的每条路径上的黑色节点就都还是一个
总结,父亲变黑,兄弟变红
3.2.2.3.2 父节点为黑
如果是这种情况的话,我们的黑色节点的数量就没法保持两个了,所以我们就只能将兄弟节点变成红色,然后递归向上调整
直到碰到红色的父节点,节点的黑色路径就能调整回来了
代码如下(下图中的uncle就是上文中的uncle节点,这里写错了):
bool erase(const K& data)
{
if (!_root)return false;
Node* del = _root;
while (del)
{
if (data < del->_data.first)
del = del->_left;
else if (data > del->_data.first)
del = del->_right;
else
break;
}
if (del == nullptr)
return false;
if (_root == del && del->_left == nullptr && del->_right == nullptr)
{
delete _root;
_root = nullptr;
return true;
}
// 有两个孩子的情况
if (del->_left && del->_right)
{
Node* rightMin = del->_right;
while (rightMin->_left)
{
rightMin = rightMin->_left;
}
del->_data = rightMin->_data;
// 将问题转换成叶子节点或者一个节点的删除
del = rightMin;
}
// 有一个孩子的情况
if ((del->_left && !del->_right) || (!del->_left && del->_right))
{
// 一个孩子的情况只能是父黑子红
// 不然不满足每条路径都只有一个黑色节点
Node* child = del->_left == nullptr ? del->_right : del->_left;
del->_data = child->_data;
del = child;
}
/ 没有孩子的情况分为红色和黑色两种
/ 红色直接删,黑色需要细分情况
if (del->_col == BLACK)
{
adjustRBTreeBalance(del);
}
Node* parent = del->_parent;
if (parent->_left == del)
parent->_left = nullptr;
else
parent->_right = nullptr;
delete del;
return true;
}
void adjustRBTreeBalance(Node* del)
{
// 前面已经判断过了,但是这里是后面代码的递归出口
// 因为后面写到兄弟为黑并没有子节点,并且父亲为黑的情况的时候,就需要递归处理
if (_root == nullptr)
return;
Node* parent = del->_parent;
Node* uncle = del == parent->_left ? parent->_right : parent->_left;
if (uncle->_col == BLACK)
{
int type = whichtype(uncle);
switch (type)
{
case LL:
uncle->_col = parent->_col;
parent->_col = uncle->_right->_col = BLACK;
RotateL(parent);
break;
case RR:
uncle->_col = parent->_col;
parent->_col = uncle->_left->_col = BLACK;
RotateR(parent);
break;
case LR:
uncle->_right->_col = parent->_col;
parent->_col = BLACK;
RotateL(uncle);
RotateR(parent);
break;
case RL:
uncle->_left->_col = parent->_col;
parent->_col = BLACK;
RotateR(uncle);
RotateL(parent);
break;
default:
// 没有红色子节点
// 需要判断父节点是什么颜色
// 如果父节点为红色,交换颜色即可
// 如果父节点为黑色, 则uncle节点变红后递归处理
if (parent->_col == RED)
{
parent->_col = BLACK;
uncle->_col = RED;
}
else
{
uncle->_col = RED;
adjustRBTreeBalance(parent);
}
break;
}
}
else
{
// uncle节点的颜色为红
if (parent->_left == uncle)
{
uncle->_col = BLACK;
uncle->_right->_col = RED;
RotateR(parent);
}
else
{
uncle->_col = BLACK;
uncle->_left->_col = RED;
RotateL(parent);
}
}
}
int whichtype(Node* uncle)
{
Node* parent = uncle->_parent;
if (uncle == parent->_left)
{
if (uncle->_left != nullptr)
{
return RR;
}
else if (uncle->_right != nullptr)
{
return LR;
}
}
else
{
if (uncle->_right != nullptr)
{
return LL;
}
else if (uncle->_left != nullptr)
{
return RL;
}
}
// 无红色节点的情况
return 0;
}
结语
看到这里,这篇博客有关红黑树的相关内容就讲完啦~( ̄▽ ̄)~*
如果觉得对你有帮助的话,希望可以多多支持博主喔(○` 3′○)