✨✨ 欢迎大家来到贝蒂大讲堂✨✨
🎈🎈养成好习惯,先赞后看哦~🎈🎈
所属专栏:数据结构与算法
贝蒂的主页:Betty's blog
1. 红黑树的介绍
1.1. 红黑树的引入
我们前面学习了AVL
树,知道其本质是通过旋转操作来确保每个节点的左右子树高度差不超过 1,以此解决数据有序或接近有序时二叉搜索树退化为单边树而导致查找效率低下的问题。然而,当插入数据量过大时,频繁的旋转操作同样会使效率降低。为应对这一问题,就有人提出了一种更为特殊的树------红黑树。
红黑树 是一种自平衡的二叉查找树,查找效率高。它由Rudolf Baye r于 1978 年发明,当时被称为平衡二叉B树。后来,在 1978 年,Leo J. Guibas 和 Robert Sedgewick对其进行了修改,形成了如今的红黑树。
1.2. 红黑树的特点
首先,红黑树属于二叉搜索树,其特点是在每个节点额外增加了一个存储位用于记录节点的颜色,该颜色可以是 RED
,也可以是 BLACK
。 并且红黑树通过对任意一条从根到叶子的简单路径上颜色的约束,能够保证最长路径不会超过最短路径的二倍,从而实现近似平衡,减少旋转的效果。其具体特点如下:
- 节点颜色只有黑色与红色两种颜色。
- 根节点一定是黑色。
- 空节点
null
一定为黑色,并且在红黑树中认为null
节点才是叶子节点。- 红色节点的子节点和父节点一定为黑色。
- 从任一节点到叶子节点的所有路径都包含相同数目的黑色节点。
为什么上述规则能保证红黑树最长路径不超过最短路径的两倍?
具体来说,根据上述规则可以得出最短路径即为全是黑节点的路径,最长路径则是一个红节点接着一个黑节点。当从根节点到叶子节点的路径上黑色节点数量相同时,最长路径恰好是最短路径的两倍。
2. 红黑树的功能
以下是红黑树常见的功能:
- 红黑树的插入。
- 红黑树的查找。
- 红黑树的删除。
3. 红黑树的结构
3.1. 红黑树的节点
红黑树的节点本质与二叉搜索树的节点差不多,所以肯定有三个成员变量:左子树_left
,右子树_right
,键值_kv
,并且键值我们采用KV
模型的形式。并且还有一个表示节点颜色的变量_col
,以及一个父节点_parent
。并且为了增加代码的可读性,我们可以定义一个枚举来表示颜色。当然为了适配不同的类型,我们同样需要定义一个模版.。
cpp
template<class K,class V>
struct RBTreeNode
{
RBTreeNode(const pair<K,V>& kv = pair<K,V>(), Color col = RED)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _kv(kv)
, _col(col)//默认为红节点
{}
RBTreeNode<K, V>* _left;//左子树
RBTreeNode<K, V>* _right;//右子树
RBTreeNode<K, V>* _parent;//父节点
pair<K, V> _kv;//键值
Color _col;//颜色
};
为什么插入新节点的颜色默认是红色而不是黑色呢?
- 若插入的是黑色节点,会导致插入路径的黑色节点数量增加,破坏原每条路径黑色节点数量相同的平衡特性,需要对所有路径进行调整,非常麻烦。
- 若插入节点为红色,则需分情况讨论。当插入节点的父节点为黑色时,不会违反红黑树规则,无需处理。而当插入节点的父节点为红色时,由于红黑树不能有连续的红色节点,此时就需要进行调整。但是因为只需调整该路径,所以明显更简单。
综上所述,红黑树插入节点默认设为红色能在一定程度上减少违反规则的情况,同时降低调整树结构的复杂性。
3.2. 红黑树
然后我们就可以通过节点来定义红黑树,并将根节点初始化为空。
cpp
template<class K,class V>
class RBTree
{
typedef RBTreeNode<K, V> Node;
public:
//...具体功能
private:
Node* _root = nullptr;//根节点
};
4. 红黑树的初始化与销毁
4.1. 构造函数/拷贝构造/赋值重载
首先我们直接定义一个无参的构造函数,因为我们在定义拷贝构造之后编译器就不会在生成默认的构造函数了。
cpp
RBTree()
{}
之后我们可以利用递归来实现一个拷贝构造函数。
cpp
RBTree(const RBTree<K, V>&t)
{
_root = copy(t._root);
}
Node* copy(Node* root)
{
// 如果原始节点为空,直接返回空指针
if (root == nullptr)
{
return nullptr;
}
// 为新节点分配内存并拷贝原始节点的值
Node* newnode = new Node(root->_kv);
// 递归拷贝左子树
newnode->_left = copy(root->_left);
// 递归拷贝右子树
newnode->_right = copy(root->_right);
// 将新节点的父节点指针置为空
newnode->_parent = nullptr;
// 拷贝原始节点的颜色信息
newnode->_col = root->_col;
// 如果新节点的左子节点存在,设置其父节点为新节点
if (newnode->_left)
{
newnode->_left->_parent = newnode;
}
// 如果新节点的右子节点存在,设置其父节点为新节点
if (newnode->_right)
{
newnode->_right->_parent = newnode;
}
}
最后我们通过一个简单的方式实现赋值重载------通过形参调用拷贝构造出一个临时变量,然后交换this
所指向的变量,这样原本this
所指向的对象出了作用域就会销毁,间接实现了实现赋值重载。
cpp
RBTree<K, V> operator =(const RBTree<K, V> t)
{
this->swap(_root, t._root);
return *this;
}
4.2. 析构函数
析构函数需要借助递归释放所有节点,而为了方便我们传参我们可以定义子函数来帮助我们解决。
cpp
~RBTree()
{
Destroy(_root);
}
void Destroy(Node*& root)
{
if (root == nullptr)
{
return;
}
//递归销毁左子树
Destroy(root->_left);
//递归销毁右子树
Destroy(root->_right);
//销毁根节点
delete root;
root = nullptr;
}
5. 红黑树的功能实现
不论是红黑树的插入还是删除操作都需要用到旋转操作,如果你还不知道旋转操作的具体内容可以先看看这篇文章------AVL树。当然具体的旋转操作也看到也有细微的区别,因为红黑树并不存在平衡因子,但是主体思路肯定不会改变。
5.1. 红黑树的插入
向红黑树进行插入,首先是先找到需要插入的位置,这个逻辑与二叉搜索树类似,这里就不在赘述。找到之后插入默认的红节点,此时就可以分为五种情况来讨论:
为了方便叙述,我们首先假设插入节点为cur
,其父节点为parent
,其父节点的兄弟节点为uncle
,其父节点的父节点为grandfather
。
其中下面图示抽象的情况可能是红黑树整体,也可能是红黑树的某个子树。
5.1.1. 情况一
第一种情况就是第一次插入,也就是插入根节点,此时根据性质1我们只需要将对应颜色从RED
改为BLACK
即可。
5.1.2. 情况二
第二种情况就是插入节点的父节点是黑节点,符合红黑树的定义,所以此时也不需要做任何调整,直接插入即可。
5.1.3. 情况三
第三种情况就是插入节点的父节点parent
是红节点,其父节点的兄弟节点uncle
也为红节点,并且祖父节点grandfather
是黑节点。如下图:
并且为了方便我们之后的null
节点不画出,默认存在。其中a,b,c,d,e
五颗子树中黑色节点数量都相同。
这种情况的调整方法也很简单,直接将parent
与uncle
变为黑色,grandfather
变为红色即可。
当然镜像对称后调整方式也是同理,所以这里就不在赘述。
注意:祖父节点 grandfather
可能不是根节点,当祖父节点 parent
变为红色节点时可能会引起上面节点的冲突(祖父节点的父节点也为红色),这时就需要循环调整。并且如果祖父节点 grandfather
是根节点,该调整方法会将根节点变为红节点,所以这时需要将根节点改回黑节点。
5.1.4. 情况四
第四种情况严格来说又可以分为两种情况:一种是cur
为红色,parent
也为红色,grandfather
也为黑色,但是uncle
不存在。如下图:
其中a,b,c
三颗子树中除了null
外没有黑色节点。
还有一种是cur
为红色,parent
也为红色,grandfather
也为黑色,uncle
存在且为黑色·。如下图:
其中a,b,c
三颗子树中黑色节点的数量比d,e
两棵子树黑色节点的数量多1。
当然这种情况肯定不是新插入红色节点造成的,因为一旦cur
是新插入节点,那么a,b
两棵子树高度为0,没有任何节点,肯定无法满足黑色节点数量比c,d,e
多1这个条件。所以这种情况肯定是子树从情况三变换得来的。
针对以上两种情况我们都先右单旋,再将parent
变为黑色,grandfather
变为红色。当然镜像对称后调整方式也是同理,将旋转改为左单旋转即可,所以这里就不在赘述。
并且调整完毕后该子树的根节点是黑节点,所以也并不需要往上调整。
5.1.5. 情况五
最后一种情况与第四种情况条件相同:就是cur
为红色,parent
也为红色,grandfather
也为黑色,uncle
不存在/存在且为黑色。只不过插入节点位置有所区别。
其中a,b,c
三颗子树中除了null
外没有黑色节点。
其中a,b,c
三颗子树中黑色节点的数量比d,e
两棵子树黑色节点的数量多1。同样与情况四相同,这种情况不可能是插入造成的,一定是由情况三变化而来。
针对以上两种情况我们都先左右双旋,最后将cur
变为黑色,grandfather
变为红色。当然镜像对称后调整方式也是同理,将旋转改为右左双旋即可,所以这里就不在赘述。
并且调整完毕后该子树的根节点是黑节点,所以也并不需要往上调整。
默认插入节点为红色,最后我们对插入操作总结如下:
- 情况一:新插入的节点为根节点,将其颜色设置为黑色。
- 情况二:新插入节点的父节点为黑色,无需调整。
- 情况三:父节点和叔父节点均为红色,将父节点和叔父节点改为黑色,祖父节点改为红色,然后以祖父节点为当前节点继续进行调整,如果祖父节点是根节点则停止调整并将其改为黑色。
- 情况四:父节点为红色,叔父节点不存在/存在为黑色,当前节点是父节点的左子节点且父节点又是祖父节点的左子节点/当前节点是父节点的右子节点且父节点又是祖父节点的右子节点。先进行左单旋/右单旋,然后将父节点改为黑色,祖父节点改为红色。
- 情况五:父节点为红色,叔父节点不存在/存在为黑色,当前节点是父节点的左子节点且父节点又是祖父节点的右子节点/当前节点是父节点的右子节点且父节点又是祖父节点的左子节点。先进行左右双旋/右左双旋,然后将当前节点改为黑色,祖父改为红色。
cpp
bool Insert(const pair<K, V>& kv)
{
//情况一:如果是根节点
if (_root == nullptr)
{
_root = new Node(kv);
_root->_col = BLACK;
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (cur->_kv.first < kv.first)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_kv.first > kv.first)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
//找到插入位置
cur = new Node(kv);
if (parent->_kv.first < kv.first)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
cur->_parent = parent;
while (parent && parent->_col == RED)
{
Node* grandfather = parent->_parent;
if (parent == grandfather->_left)
{
Node* uncle = grandfather->_right;
//情况三:如果叔叔存在且为红
if (uncle && uncle->_col == RED)
{
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = cur->_parent;
}
else
{
//情况四:叔叔不存在/存在且为黑,且cur在parent的左侧
if (cur == parent->_left)
{
// g
// p u
// c
RotateR(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else//情况五:叔叔不存在 / 存在且为黑,cur在parent的右侧
{
// g
// p u
// c
RotateLR(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
//这时该子树的根节点变为黑色,不需要继续调整
break;
}
}
else
{
Node* uncle = grandfather->_left;
//情况三:如果叔叔存在且为红
if (uncle && uncle->_col == RED)
{
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
//继续调整
cur = grandfather;
parent = cur->_parent;
}
else
{
//情况四:叔叔不存在/存在且为黑,且cur在parent的左侧
if (cur == parent->_right)
{
// g
// u p
// c
RotateL(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else // 情况五:叔叔不存在 / 存在且为黑,cur在parent的右侧
{
// g
// u p
// c
RotateRL(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
//这时该子树的根节点变为黑色,不需要继续调整
break;
}
}
}
//防止情况三改到根节点变为红色
_root->_col = BLACK;
return true;
}
void RotateR(Node* parent)
{
Node* cur = parent;
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR;
if (subLR)
subLR->_parent = parent;
subL->_right = parent;
Node* ppNode = parent->_parent;
parent->_parent = subL;
if (parent == _root)
{
_root = subL;
_root->_parent = nullptr;
}
else
{
if (ppNode->_left == parent)
{
ppNode->_left = subL;
}
else
{
ppNode->_right = subL;
}
subL->_parent = ppNode;
}
}
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
parent->_right = subRL;
if (subRL)
subRL->_parent = parent;
subR->_left = parent;
Node* ppNode = parent->_parent;
parent->_parent = subR;
if (parent == _root)
{
_root = subR;
_root->_parent = nullptr;
}
else
{
if (ppNode->_right == parent)
{
ppNode->_right = subR;
}
else
{
ppNode->_left = subR;
}
subR->_parent = ppNode;
}
}
void RotateLR(Node* parent)
{
RotateL(parent->_left);
RotateR(parent);
}
void RotateRL(Node* parent)
{
RotateR(parent->_right);
RotateL(parent);
}
5.2. 红黑树的查找
红黑树的查找的逻辑本质与二叉搜索树的查找逻辑相同,所以这里就不在赘述。
cpp
Node* Find(const K& val)
{
Node* cur = _root;
while (cur)
{
if (cur->_kv.first > val)
{
//左子树中查找
cur = cur->_left;
}
else if (cur->_kv.first < val)
{
//右子树中查找
cur = cur->_right;
}
else
{
//找到了
return cur;
}
}
return nullptr;
}
5.3. 红黑树的删除
删除红黑树的逻辑明显比插入逻辑更加复杂,但是总体逻辑又与AVL
树与二叉搜索树类似,首先第一步找到待删除节点 ,如果待删除节点的左右子树都不为空,可以采用伪删除法,即寻找到左子树的最右节点(左子树的最大值),或者是右子树的最左节点(右子树的最小值),然后赋值,转换为在其左子树或者右子树删除节点,保证待删除节点的左右子树至少有一个为空。第二步进行调整 ,删除该节点可能会破坏红黑树的性质所以这时需要调整。第三步删除节点并重新链接。
第一步与第三步都非常简单,我们接下来重点讨论第三步。
为了方便描述我们将待删除节点称为delete
,其父节点称为parent
,其兄弟节点称为brother
,其兄弟节点的左右节点分别称为broLeft
与broRight
。
看图需知:
- 如果
parent
为蓝色,表示parent
可能为黑色也可能为红色。- 如果
broLeft
或broRight
为蓝色,表示broLeft
或broRight
可能为黑色也可能为红色,也可能为null
节点。- 如果
broLeft
或broRight
为黑色,表示broLeft
或broRight
可能为黑色也可能为null
节点。- 绿色长方形表示高度未知的子树,也可以为
null
节点。
下面图示我们将按照delete
节点在parent
节点左侧的形式讨论,根据镜像对称delete
节点在parent
节点右侧只需对称操作即可,所以这里就不在赘述。
首先如果delete
节点为红色,因为至少有一个空节点,那么另一个子节点既不能为红色也不可能为黑色,此时直接删除即可。
其次如果delete
节点为黑色,且有一个孩子节点,那么这个孩子节点一定为红色,(parent
与brother
不都为红色)否则会破坏性质5。这种情况也只需重新链接不需要调整,然后将该孩子节点改为黑色。
接下来我们将讨论delete
节点为黑色且没有孩子节点的情况。
- 情况一:兄弟节点为红色。
因为兄弟节点为红色,其父节点一定为黑色,所以对于这种情况我们只需要对parent
节点进行一次左旋转,再将brother
变为黑色,parent
变为红色。再重新更新brother
节点,继续分析原删除节点delete
,将情况一转换为情况二,三,四。
- 情况二:兄弟节点为黑色,且其左右孩子为空或者都为黑色。
对于这种情况我们需要将brother
变为红色,然后根据parent
节点颜色继续分析,如果parent
节点为红色,只需要将其变为黑色即可,调整结束;如果parent
节点为黑色,那么delete
节点被删除后,原来该子树有两个黑色节点变为一个,性质5被破坏,将parent
节点当做删除节点往上更新或者遇见根节点直接停止,将情况二转换为情况一,二,三,四。
- 情况三:兄弟节点为黑色,且其左孩子是红色结点,右孩子是黑色结点或为空。
这种情况一定不是第一次调整,因为如果是第一次调整此时根本不平衡。对应这种情况我们需要对brother
节点进行一次右单旋,然后再将brother
结点变为红色,broLeft
变为黑色,最后更新brother
节点,再对delete
节点进行调整,情况三就转换成了情况四。
- 情况四:兄弟节点为黑色,且其右孩子是红色结点。
对于这种情况我们先将parent
节点进行一次左单旋,然后将parent
的颜色赋值给brother
,再将parent
的颜色变为黑色,最后将broRight
变为黑色,此时红黑树的调整一定结束。
最后删除节点如果是根节点直接删除再将左子树或者右子树的根节点变黑,当做新的根节点。如果没有左右子树都为空,那就将根节点_root
置为空。
最后我们来总结
- 如果删除节点为红色,直接删除。
- 如果删除节点为黑色且有一个子节点,直接删除再链接,并将该子节点改为黑色。
- 如果删除节点为黑色且没有子节点,可以分为以下四种情况:
- 情况一:兄弟节点为红色。先对
parent
左单旋,再将brother
变为黑色,将parent
变为红色,再对待删除结点delete
进行情况分析,情况一就转换成了情况二、三或四。- 情况二:兄弟节点为黑色,且其左右孩子为空或者都为黑色。我们直接将
brother
变成红色,若parent
是红色,将parent
变为黑色后调整结束;若parent
是黑色,则需将parent
结点当作下一次调整时的结点进行分析直至根节点,情况二就转换成了情况一,二、三或四。- 情况三:兄弟节点为黑色,且其左孩子是红色结点,右孩子是黑色结点或为空。先将
brother
右单旋,再将brother
变为红色,broLeft
变为黑色,再对待删除结点delete
进行情况分析,将情况三转换成情况四。- 情况四:兄弟节点为黑色,且其右孩子是红色结点。先将
parent
左单旋,然后将parent
的颜色赋值给brother
,再将parent
变为黑色,最后将brotRight
变为黑色,此时红黑树的调整一定结束。
思考题:为什么删除节点为黑色且没有子节点的四种情况的调整方法一定能使红黑树调整成功?
我们知道这四种调整方法一共有三个"出口",分别为:(1)情况二调整后parent
为红色,(2)情况二parent
为黑色调整到根节点,(3)调整完情况四。所以问题就成功转换为经过任意三个"出口"结束之后,为什么红黑树一定调整成功。
- 如果从1号"出口"结束:一个子节点被删除后,因为使
parent
变为黑色,另一个子节点变为红色,所以此时这颗子树的路径上的黑色节点数没减少,调整成功。- 如果从2号"出口"结束:一定是只经过情况二,因为如果是从情况一转换过来的情况二,那
parent
一定是红色就会从1号"出口"结束。如果只经过情况二那么每次操作都会使上层的一颗子树(非父节点所在子树)黑色节点数减一,最后更新到根节点整颗红黑树根节点到叶子节点的黑色节点数比原来少1,调整成功。- 如果从3号"出口"结束:经过第四种调整方式,被删除一侧的黑色节点数增一,无论是从第一,二,三删除节点后都会使被删除一侧减一,所以经过情况四调整成功。
思考题:为什么删除节点为黑色且没有子节点的四种情况的调整方法不会造成死循环?
因为情况三一定会被转换为情况四,情况四一定会调整成功,所以情况三与情况四一定不会死循环。而情况一与情况二也可能转换为情况三与情况四这时也不会死循环。但会不会出现情况一与情况二一直互相循环的情况呢?其实不会的,因为一旦从情况一转换为情况二,此时父节点
parent
为红色,一定会结束。
cpp
//删除函数
bool Erase(const K& key)
{
//(一)找到删除节点
Node* cur = Find(key);
//未找到返回false
if (cur == nullptr)
{
return false;
}
//记录父节点
Node* parent = cur->_parent;
//用于标记实际的待删除结点及其父结点
Node* delParent = nullptr;
Node* delCur = nullptr;
if (cur->_left == nullptr) //待删除结点的左子树为空
{
if (cur == _root) //待删除结点是根结点
{
_root = _root->_right; //让根结点的右子树作为新的根结点
if (_root)
{
_root->_parent = nullptr;
_root->_col = BLACK; //根结点为黑色
}
delete cur; //删除原根结点
return true;
}
else
{
delParent = parent; //标记实际删除结点的父结点
delCur = cur; //标记实际删除的结点
}
}
else if (cur->_right == nullptr) //待删除结点的右子树为空
{
if (cur == _root) //待删除结点是根结点
{
_root = _root->_left; //让根结点的左子树作为新的根结点
if (_root)
{
_root->_parent = nullptr;
_root->_col = BLACK; //根结点为黑色
}
delete cur; //删除原根结点
return true;
}
else
{
delParent = parent; //标记实际删除结点的父结点
delCur = cur; //标记实际删除的结点
}
}
else //待删除结点的左右子树均不为空
{
//替换法删除
//寻找待删除结点右子树当中key值最小的结点作为实际删除结点
Node* minParent = cur;
Node* minRight = cur->_right;
while (minRight->_left)
{
minParent = minRight;
minRight = minRight->_left;
}
cur->_kv = minRight->_kv; //将待删除结点的键值改为minRight的键值
delParent = minParent; //标记实际删除的父节点
delCur = minRight; //标记实际删除的结点
}
//记录待删除结点及其父结点,便于后面删除
Node* del = delCur;
Node* delP = delParent;
//(二)调整红黑树
AdjustRBTree(delCur, delParent);
//(三)进行实际删除
DeleteNode(del, delP);
return true;
}
void DeleteNode(Node* del, Node* delP)
{
if (del->_left == nullptr) //实际删除结点的左子树为空
{
if (del == delP->_left) //实际删除结点是其父结点的左孩子
{
delP->_left = del->_right;
//指向父节点
if (del->_right)
del->_right->_parent = delP;
}
else //实际删除结点是其父结点的右孩子
{
delP->_right = del->_right;
if (del->_right)
del->_right->_parent = delP;
}
}
else //实际删除结点的右子树为空
{
if (del == delP->_left) //实际删除结点是其父结点的左孩子
{
delP->_left = del->_left;
if (del->_left)
del->_left->_parent = delP;
}
else //实际删除结点是其父结点的右孩子
{
delP->_right = del->_left;
if (del->_left)
del->_left->_parent = delP;
}
}
delete del; //实际删除结点
}
void AdjustRBTree(Node* delCur, Node* delParent)
{
if (delCur->_col == BLACK) //删除的是黑色结点
{
if (delCur->_left) //待删除结点有一个红色的左孩子(不可能是黑色)
{
delCur->_left->_col = BLACK; //将这个红色的左孩子变黑即可
}
else if (delCur->_right) //待删除结点有一个红色的右孩子(不可能是黑色)
{
delCur->_right->_col = BLACK; //将这个红色的右孩子变黑即可
}
else //待删除结点的左右均为空
{
while (delCur != _root) //可能一直调整到根结点
{
if (delCur == delParent->_left) //待删除结点是其父结点的左孩子
{
Node* brother = delParent->_right; //兄弟结点是其父结点的右孩子
//情况一:brother为红色
if (brother->_col == RED)
{
delParent->_col = RED;
brother->_col = BLACK;
RotateL(delParent);
//需要继续处理
brother = delParent->_right; //更新brother
}
//情况二:brother为黑色,且其左右孩子都是黑色结点或为空
if (((brother->_left == nullptr) || (brother->_left->_col == BLACK))
&& ((brother->_right == nullptr) || (brother->_right->_col == BLACK)))
{
brother->_col = RED;
if (delParent->_col == RED)
{
delParent->_col = BLACK;
break;
}
//需要继续处理
delCur = delParent;
delParent = delCur->_parent;
}
else
{
//情况三:brother为黑色,且其左孩子是红色结点,右孩子是黑色结点或为空
if ((brother->_right == nullptr) || (brother->_right->_col == BLACK))
{
brother->_left->_col = BLACK;
brother->_col = RED;
RotateR(brother);
//需要继续处理
brother = delParent->_right; //更新brother
}
//情况四:brother为黑色,且其右孩子是红色结点
brother->_col = delParent->_col;
delParent->_col = BLACK;
brother->_right->_col = BLACK;
RotateL(delParent);
break; //情况四执行完毕后调整一定结束
}
}
else //delCur == delParent->_right //待删除结点是其父结点的右孩子
{
Node* brother = delParent->_left; //兄弟结点是其父结点的左孩子
//情况一:brother为红色
if (brother->_col == RED) //brother为红色
{
delParent->_col = RED;
brother->_col = BLACK;
RotateR(delParent);
//需要继续处理
brother = delParent->_left; //更新brother
}
//情况二:brother为黑色,且其左右孩子都是黑色结点或为空
if (((brother->_left == nullptr) || (brother->_left->_col == BLACK))
&& ((brother->_right == nullptr) || (brother->_right->_col == BLACK)))
{
brother->_col = RED;
if (delParent->_col == RED)
{
delParent->_col = BLACK;
break;
}
//需要继续处理
delCur = delParent;
delParent = delCur->_parent;
}
else
{
//情况三:brother为黑色,且其右孩子是红色结点,左孩子是黑色结点或为空
if ((brother->_left == nullptr) || (brother->_left->_col == BLACK))
{
brother->_right->_col = BLACK;
brother->_col = RED;
RotateL(brother);
//需要继续处理
brother = delParent->_left; //更新brother
}
//情况四:brother为黑色,且其左孩子是红色结点
brother->_col = delParent->_col;
delParent->_col = BLACK;
brother->_left->_col = BLACK;
RotateR(delParent);
break; //情况四执行完毕后调整一定结束
}
}
}
}
}
}
6. 判断是否为红黑树
判断是否为红黑树首先得判断是否为二叉搜索树,然后判断是否满足红黑树的性质。
判断是否满足红黑树的性质,首先判断根节点是否为黑色。然后统计一条路径的节点个数,最后通过递归判断是否有连续的红色节点,以及每条路径的黑色节点是否相同。
cpp
bool IsRBTree()
{
if (_root->_col == RED)
{
cout << "根节点为红节点" << endl;
return false;
}
//从根节点到叶节点的黑色节点数
int refNum = 0;
Node* cur = _root;
while (cur)
{
if (cur->_col == BLACK)
{
++refNum;
}
cur = cur->_left;
}
return Check(_root, 0, refNum) && isValidBST();
}
//判断是否为二叉搜索树
bool isValidBST()
{
return _isValidBST(_root, LONG_MIN, LONG_MAX);
}
bool _isValidBST(Node* root, long long lower, long long upper)
{
if (root == nullptr)
{
return true;
}
if (root->_kv.first <= lower || root->_kv.first >= upper)
{
return false;
}
bool left = _isValidBST(root->_left, lower, root->_kv.first);
bool right = _isValidBST(root->_right, root->_kv.first, upper);
return left && right;
}
bool Check(Node* root, int blackNum, int refNum)
{
if (root == nullptr)
{
//判断黑色节点数是否相同
if (refNum != blackNum)
{
cout << "存在黑色节点的数量不相等的路径" << endl;
return false;
}
return true;
}
//判断是否存在连续的红色节点
if (root->_parent && root->_col == RED && root->_parent->_col == RED)
{
cout << root->_kv.first << "存在连续的红色节点" << endl;
return false;
}
if (root->_col == BLACK)
{
blackNum++;
}
return Check(root->_left, blackNum, refNum)
&& Check(root->_right, blackNum, refNum);
}
7. 复杂度分析
对于红黑树的插入、查找和删除操作,其时间复杂度和空间复杂度分析如下:
时间复杂度:
- Insert操作:平均和最坏情况下的时间复杂度均为 O ( log n ) O(\log n) O(logn)。插入过程中需要调整红黑树的性质,可能涉及到旋转操作,但这些操作的时间是常数级,而查找插入位置的过程类似于二叉搜索树,时间复杂度为 O ( log n ) O(\log n) O(logn)。
- Find操作:平均和最坏情况下的时间复杂度均为 O ( log n ) O(\log n) O(logn)。与二叉搜索树的查找过程类似,通过比较键值逐渐缩小搜索范围,每次比较都能将范围缩小一半。
- Erase操作:平均和最坏情况下的时间复杂度均为 O ( log n ) O(\log n) O(logn)。删除节点时需要查找节点位置,然后根据红黑树的性质进行调整和可能的旋转操作,这些操作的时间复杂度与插入操作相似。
空间复杂度:
- Insert操作:空间复杂度为 O ( 1 ) O(1) O(1)。在插入过程中,主要的额外空间消耗在于创建新节点以及存储一些临时变量,如指针等,这些空间消耗是常数级的。
- Find操作:空间复杂度为 O ( 1 ) O(1) O(1)。寻找过程中仅使用了少量的固定数量的指针和临时变量,没有额外的大规模空间分配。
- Erase操作:空间复杂度为 O ( 1 ) O(1) O(1)。删除操作中的主要空间消耗在于存储临时变量和指针,不会动态分配大规模的额外空间。
8.源码
cpp
#pragma once
#include<utility>
enum _col
{
RED,//红色
BLACK//黑色
};
template<class K,class V>
struct RBNode
{
RBNode(const pair<K,V>& kv = pair<K,V>(), _col col = RED)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _kv(kv)
, _col(col)//默认为红节点
{}
RBNode<K, V>* _left;//左子树
RBNode<K, V>* _right;//右子树
RBNode<K, V>* _parent;//父节点
pair<K, V> _kv;//键值
_col _col;//颜色
};
template<class K, class V>
class RBTree
{
typedef RBNode<K, V> Node;
public:
RBTree()
{}
RBTree(const RBTree<K, V>&t)
{
_root = copy(t._root);
}
RBTree<K, V> operator =(const RBTree<K, V> t)
{
this->swap(_root, t._root);
return *this;
}
bool Insert(const pair<K, V>& kv)
{
//情况一:如果是根节点
if (_root == nullptr)
{
_root = new Node(kv);
_root->_col = BLACK;
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (cur->_kv.first < kv.first)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_kv.first > kv.first)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
//找到插入位置
cur = new Node(kv);
if (parent->_kv.first < kv.first)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
cur->_parent = parent;
while (parent && parent->_col == RED)
{
Node* grandfather = parent->_parent;
if (parent == grandfather->_left)
{
Node* uncle = grandfather->_right;
//情况三:如果叔叔存在且为红
if (uncle && uncle->_col == RED)
{
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = cur->_parent;
}
else
{
//情况四:叔叔不存在/存在且为黑,且cur在parent的左侧
if (cur == parent->_left)
{
// g
// p u
// c
RotateR(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else//情况五:叔叔不存在 / 存在且为黑,cur在parent的右侧
{
// g
// p u
// c
RotateLR(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
//这时该子树的根节点变为黑色,不需要继续调整
break;
}
}
else
{
Node* uncle = grandfather->_left;
//情况三:如果叔叔存在且为红
if (uncle && uncle->_col == RED)
{
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
//继续调整
cur = grandfather;
parent = cur->_parent;
}
else
{
//情况四:叔叔不存在/存在且为黑,且cur在parent的左侧
if (cur == parent->_right)
{
// g
// u p
// c
RotateL(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else // 情况五:叔叔不存在 / 存在且为黑,cur在parent的右侧
{
// g
// u p
// c
RotateRL(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
//这时该子树的根节点变为黑色,不需要继续调整
break;
}
}
}
//防止情况三改到根节点变为红色
_root->_col = BLACK;
return true;
}
void RotateR(Node* parent)
{
Node* cur = parent;
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR;
if (subLR)
subLR->_parent = parent;
subL->_right = parent;
Node* ppNode = parent->_parent;
parent->_parent = subL;
if (parent == _root)
{
_root = subL;
_root->_parent = nullptr;
}
else
{
if (ppNode->_left == parent)
{
ppNode->_left = subL;
}
else
{
ppNode->_right = subL;
}
subL->_parent = ppNode;
}
}
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
parent->_right = subRL;
if (subRL)
subRL->_parent = parent;
subR->_left = parent;
Node* ppNode = parent->_parent;
parent->_parent = subR;
if (parent == _root)
{
_root = subR;
_root->_parent = nullptr;
}
else
{
if (ppNode->_right == parent)
{
ppNode->_right = subR;
}
else
{
ppNode->_left = subR;
}
subR->_parent = ppNode;
}
}
void RotateLR(Node* parent)
{
RotateL(parent->_left);
RotateR(parent);
}
void RotateRL(Node* parent)
{
RotateR(parent->_right);
RotateL(parent);
}
Node* Find(const K& val)
{
Node* cur = _root;
while (cur)
{
if (cur->_kv.first > val)
{
//左子树中查找
cur = cur->_left;
}
else if (cur->_kv.first < val)
{
//右子树中查找
cur = cur->_right;
}
else
{
//找到了
return cur;
}
}
return nullptr;
}
//删除函数
bool Erase(const K& key)
{
//(一)找到删除节点
Node* cur = Find(key);
//未找到返回false
if (cur == nullptr)
{
return false;
}
//记录父节点
Node* parent = cur->_parent;
//用于标记实际的待删除结点及其父结点
Node* delParent = nullptr;
Node* delCur = nullptr;
if (cur->_left == nullptr) //待删除结点的左子树为空
{
if (cur == _root) //待删除结点是根结点
{
_root = _root->_right; //让根结点的右子树作为新的根结点
if (_root)
{
_root->_parent = nullptr;
_root->_col = BLACK; //根结点为黑色
}
delete cur; //删除原根结点
return true;
}
else
{
delParent = parent; //标记实际删除结点的父结点
delCur = cur; //标记实际删除的结点
}
}
else if (cur->_right == nullptr) //待删除结点的右子树为空
{
if (cur == _root) //待删除结点是根结点
{
_root = _root->_left; //让根结点的左子树作为新的根结点
if (_root)
{
_root->_parent = nullptr;
_root->_col = BLACK; //根结点为黑色
}
delete cur; //删除原根结点
return true;
}
else
{
delParent = parent; //标记实际删除结点的父结点
delCur = cur; //标记实际删除的结点
}
}
else //待删除结点的左右子树均不为空
{
//替换法删除
//寻找待删除结点右子树当中key值最小的结点作为实际删除结点
Node* minParent = cur;
Node* minRight = cur->_right;
while (minRight->_left)
{
minParent = minRight;
minRight = minRight->_left;
}
cur->_kv = minRight->_kv; //将待删除结点的键值改为minRight的键值
delParent = minParent; //标记实际删除的父节点
delCur = minRight; //标记实际删除的结点
}
//记录待删除结点及其父结点,便于后面删除
Node* del = delCur;
Node* delP = delParent;
//(二)调整红黑树
AdjustRBTree(delCur, delParent);
//(三)进行实际删除
DeleteNode(del, delP);
return true;
}
void DeleteNode(Node* del, Node* delP)
{
if (del->_left == nullptr) //实际删除结点的左子树为空
{
if (del == delP->_left) //实际删除结点是其父结点的左孩子
{
delP->_left = del->_right;
//指向父节点
if (del->_right)
del->_right->_parent = delP;
}
else //实际删除结点是其父结点的右孩子
{
delP->_right = del->_right;
if (del->_right)
del->_right->_parent = delP;
}
}
else //实际删除结点的右子树为空
{
if (del == delP->_left) //实际删除结点是其父结点的左孩子
{
delP->_left = del->_left;
if (del->_left)
del->_left->_parent = delP;
}
else //实际删除结点是其父结点的右孩子
{
delP->_right = del->_left;
if (del->_left)
del->_left->_parent = delP;
}
}
delete del; //实际删除结点
}
void AdjustRBTree(Node* delCur, Node* delParent)
{
if (delCur->_col == BLACK) //删除的是黑色结点
{
if (delCur->_left) //待删除结点有一个红色的左孩子(不可能是黑色)
{
delCur->_left->_col = BLACK; //将这个红色的左孩子变黑即可
}
else if (delCur->_right) //待删除结点有一个红色的右孩子(不可能是黑色)
{
delCur->_right->_col = BLACK; //将这个红色的右孩子变黑即可
}
else //待删除结点的左右均为空
{
while (delCur != _root) //可能一直调整到根结点
{
if (delCur == delParent->_left) //待删除结点是其父结点的左孩子
{
Node* brother = delParent->_right; //兄弟结点是其父结点的右孩子
//情况一:brother为红色
if (brother->_col == RED)
{
delParent->_col = RED;
brother->_col = BLACK;
RotateL(delParent);
//需要继续处理
brother = delParent->_right; //更新brother
}
//情况二:brother为黑色,且其左右孩子都是黑色结点或为空
if (((brother->_left == nullptr) || (brother->_left->_col == BLACK))
&& ((brother->_right == nullptr) || (brother->_right->_col == BLACK)))
{
brother->_col = RED;
if (delParent->_col == RED)
{
delParent->_col = BLACK;
break;
}
//需要继续处理
delCur = delParent;
delParent = delCur->_parent;
}
else
{
//情况三:brother为黑色,且其左孩子是红色结点,右孩子是黑色结点或为空
if ((brother->_right == nullptr) || (brother->_right->_col == BLACK))
{
brother->_left->_col = BLACK;
brother->_col = RED;
RotateR(brother);
//需要继续处理
brother = delParent->_right; //更新brother
}
//情况四:brother为黑色,且其右孩子是红色结点
brother->_col = delParent->_col;
delParent->_col = BLACK;
brother->_right->_col = BLACK;
RotateL(delParent);
break; //情况四执行完毕后调整一定结束
}
}
else //delCur == delParent->_right //待删除结点是其父结点的右孩子
{
Node* brother = delParent->_left; //兄弟结点是其父结点的左孩子
//情况一:brother为红色
if (brother->_col == RED) //brother为红色
{
delParent->_col = RED;
brother->_col = BLACK;
RotateR(delParent);
//需要继续处理
brother = delParent->_left; //更新brother
}
//情况二:brother为黑色,且其左右孩子都是黑色结点或为空
if (((brother->_left == nullptr) || (brother->_left->_col == BLACK))
&& ((brother->_right == nullptr) || (brother->_right->_col == BLACK)))
{
brother->_col = RED;
if (delParent->_col == RED)
{
delParent->_col = BLACK;
break;
}
//需要继续处理
delCur = delParent;
delParent = delCur->_parent;
}
else
{
//情况三:brother为黑色,且其右孩子是红色结点,左孩子是黑色结点或为空
if ((brother->_left == nullptr) || (brother->_left->_col == BLACK))
{
brother->_right->_col = BLACK;
brother->_col = RED;
RotateL(brother);
//需要继续处理
brother = delParent->_left; //更新brother
}
//情况四:brother为黑色,且其左孩子是红色结点
brother->_col = delParent->_col;
delParent->_col = BLACK;
brother->_left->_col = BLACK;
RotateR(delParent);
break; //情况四执行完毕后调整一定结束
}
}
}
}
}
}
~RBTree()
{
Destroy(_root);
}
bool IsRBTree()
{
if (_root->_col == RED)
{
cout << "根节点为红节点" << endl;
return false;
}
//从根节点到叶节点的黑色节点数
int refNum = 0;
Node* cur = _root;
while (cur)
{
if (cur->_col == BLACK)
{
++refNum;
}
cur = cur->_left;
}
return Check(_root, 0, refNum)&& isValidBST();
}
//判断是否为二叉搜索树
bool isValidBST()
{
return _isValidBST(_root, LONG_MIN, LONG_MAX);
}
private:
bool _isValidBST(Node* root, long long lower, long long upper)
{
if (root == nullptr)
{
return true;
}
if (root->_kv.first <= lower || root->_kv.first >= upper)
{
return false;
}
bool left = _isValidBST(root->_left, lower, root->_kv.first);
bool right = _isValidBST(root->_right, root->_kv.first, upper);
return left && right;
}
bool Check(Node* root, int blackNum, int refNum)
{
if (root == nullptr)
{
if (refNum != blackNum)
{
cout << "存在黑色节点的数量不相等的路径" << endl;
return false;
}
return true;
}
if (root->_parent && root->_col == RED && root->_parent->_col == RED)
{
cout << root->_kv.first << "存在连续的红色节点" << endl;
return false;
}
if (root->_col == BLACK)
{
blackNum++;
}
return Check(root->_left, blackNum, refNum)
&& Check(root->_right, blackNum, refNum);
}
void Destroy(Node*& root)
{
if (root == nullptr)
{
return;
}
//递归销毁左子树
Destroy(root->_left);
//递归销毁右子树
Destroy(root->_right);
//销毁根节点
delete root;
root = nullptr;
}
Node* copy(Node* root)
{
// 如果原始节点为空,直接返回空指针
if (root == nullptr)
{
return nullptr;
}
// 为新节点分配内存并拷贝原始节点的值
Node* newnode = new Node(root->_kv);
// 递归拷贝左子树
newnode->_left = copy(root->_left);
// 递归拷贝右子树
newnode->_right = copy(root->_right);
// 将新节点的父节点指针置为空
newnode->_parent = nullptr;
// 拷贝原始节点的颜色信息
newnode->_col = root->_col;
// 如果新节点的左子节点存在,设置其父节点为新节点
if (newnode->_left)
{
newnode->_left->_parent = newnode;
}
// 如果新节点的右子节点存在,设置其父节点为新节点
if (newnode->_right)
{
newnode->_right->_parent = newnode;
}
}
Node* _root = nullptr;
};