目录
情况二: cur为红,p为红,g为黑,u不存在/u存在且为黑
情况2.1:p是g的左,cur是p的左。u不存在/u存在且为黑
情况2.2:p是g的右,cur是p的右。u不存在/u存在且为黑
情况2.3:p是g的左,cur是p的右。u不存在/u存在且为黑
情况2.4:p是g的右,cur是p的左。u不存在/u存在且为黑
红黑树的概念
红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或 Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制(具体什么限制就是红黑树的性质),红黑树确保没有一条路径会比其他路径长出两倍,因而是接近平衡的。
(红黑树和AVL树都是二叉搜索树,它们的目的都是为了降低树的高度,使其高度接近logN,提高查找效率。只是AVL树要求更严格(可以理解为过于严格):每个结点的左右子树高度差不超过1,这也导致AVL树插入或删除时旋转次数过多,效率较低(当树的结构经常改变时)。而红黑树是要求没有一条路径的长度大于其他路径长度的二倍,以此确保这个搜索树接近平衡,没有AVL树严格,因此也减少了旋转次数,提高效率(具体看后面))

红黑树的性质:
1. 每个结点不是红色就是黑色
2. 根节点是黑色的
3. 如果一个节点是红色的,则它的两个孩子结点是黑色的
4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点
5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点) (可忽略)
红黑树性质分析:
回顾红黑树的概念:红黑树通过确保没有一条路径会比其他路径长出两倍,以此达到接近平衡。
那么,红黑树的性质如何确保这个结果呢?第四点等价于:根节点到后代叶子节点的路径中,黑色结点的个数相同(此处的叶子节点,可以理解为第五点的空结点,也可以理解为常规叶子结点,都一样)。黑色结点的个数相同,也就是最短的路径可以理解为x个黑,而最长的就是黑红黑红黑红...共x对(此处也因为第三点的限制:树中不能有连续的红色结点)。因此最短,最长路径的长度正好是二倍关系,其他路径的长度也就能保证在这个范围之内,从而确保红黑树接近平衡。
因此,只要确保第四点:每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点。就可以确保没有一条路径超过其他路径长度的两倍。从而确保红黑树接近平衡,以此提高搜索效率。当然,其他的性质也要遵守,只是第四点相对关键。
红黑树结点定义:
enum Color
{
RED,
BLACK
};
template< class K, class V >
struct RBTreeNode
{
RBTreeNode(const pair<K, V>& kv)
:_left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv)
{ }
RBTreeNode<K, V>* _left;
RBTreeNode* _right;
RBTreeNode* _parent;
pair<K, V> _kv;
Color _col;
};
还是那一套,没啥意思,加了一个枚举数据成员,用于保存结点颜色。
红黑树的插入:
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 != nullptr) {
if(kv.first > cur->_kv.first) {
parent = cur;
cur = cur->_right;
}
else if(kv.first < cur->_kv.first) {
parent = cur;
cur = cur->_left;
}
else {
return false;
}
}
cur = new Node(kv);
cur->_col = RED;
if(kv.first > parent->_kv.first) {
parent->_right = cur;
}
else {
parent->_left = cur;
}
cur->_parent = parent;
// 插入新结点成功,且为红色。进行判断
// 进入下方循环后,parent一定为红,则parent一定不是根节点,则parent一定有父亲,有没有兄弟不一定。
while(parent && parent->_col == RED) {
Node* grandfather = parent->_parent;
Node* uncle = nullptr;
if(parent == grandfather->_left)
uncle = grandfather->_right;
else
uncle = grandfather->_left;
// 判断叔叔的情况,决定处理方式
if(uncle && uncle->_col == RED) {
// 叔叔存在且为红
parent->_col = BLACK;
uncle->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = cur->_parent;
}
else {
// 叔叔不存在或者叔叔存在且为黑
if(parent == grandfather->_left && cur == parent->_left) {
// 此时,左左,右单旋+变色
// 先变色也可以
parent->_col = BLACK;
grandfather->_col = RED;
RotateR(grandfather);
}
else if(parent == grandfather->_right && cur == parent->_right) {
// 右右,左单旋
parent->_col = BLACK;
grandfather->_col = RED;
RotateL(grandfather);
}
else if(parent == grandfather->_right && cur == parent->_left) {
// cur为红,parent为红,grandfather为黑。
// 右左双旋。
// RotateR(parent);
// RotateL(grandfather);
// // 记住这里是上黑,下面俩红即可。
// cur->_col = BLACK;
// grandfather->_col = RED;
// 第二种实现方法,即单旋后变为双旋。
RotateR(parent);
std::swap(cur, parent);
parent->_col = BLACK;
grandfather->_col = RED;
RotateL(grandfather);
}
else if(parent == grandfather->_left && cur == parent->_right) {
// RotateL(parent);
// RotateR(grandfather);
// // 记住这里是上黑,下面俩红即可。
// cur->_col = BLACK;
// grandfather->_col = RED;
RotateL(parent);
std::swap(cur, parent);
parent->_col = BLACK;
grandfather->_col = RED;
RotateR(grandfather);
}
break;
}
if(cur == _root) {
cur->_col = BLACK;
}
}
return true;
}
红黑树是一种二叉搜索树,因此,和AVL树一样,开始的插入逻辑都是二叉搜索树的插入方式,找到位置之后插入新建结点。
在讲红黑树的插入之前,有一个点很重要。即我们必须意识到:红黑树通过满足性质和规定,从而保证没有一条路径超过其他路径长度的二倍,从而保证树的高度接近logN,从而确保树的查找效率。因此,我们在考虑红黑树的插入时,只需要确保怎么才能不破坏红黑树的性质,破坏之后怎么处理即可。
新插入结点应当定为黑色还是红色?
若新插入结点定为黑色,则违背红黑树规定4。若新插入结点定为红色,则可能违背规定3。
违背规定四:导致从根节点到该新插入结点的路径中黑色结点个数比其他路径都多1,这时候是很难处理的。而违背规定三也只是概率性问题,只有当新插入结点的父节点为红色时,才需要处理,且这里的处理方式也一定比处理规定四简单很多。(事实上,插入黑色结点违背规定四如何处理我并不知道,但是新插入红色结点总是合适的)
故,我们将每个除了根节点之外的新结点都定为红色。
新插入结点可能的情况:
1. 整棵树为空树,直接插入黑色根节点即可。
2. 新插入红色结点的父节点为黑色,插入成功。
3. 新插入红色结点的父节点为红色,违背性质3,此时需要采取措施。
下面我们讨论当新插入结点的父节点为红色时,可能的情况及相应的操作:
红黑树插入红色新结点后,违背规则三的可能情况及相应措施:
首先,新插入结点一定为红色,且此时父节点为红色,祖父结点为黑色。这是一定的。(逻辑很简单)
我们考虑的是此时新插入结点的叔叔结点的情况,从而对应不同操作:
约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点
情况一:cur为红,p为红,g为黑,u存在且为红
抽象图:
操作(处理方式):
1. p变为黑,u变为黑,g变为红。
2. 若g为整颗红黑树的根节点,则再将其变为黑。
(因为红黑树规定根节点必须为黑,此时变为黑色之后,也就相当于每条路径多一个黑色结点,并不影响红黑树的性质)
3. 若g为子树的根节点,则将其当作cur,继续向上循环检查。
(因为此时处理完之后,对于这颗子树来说,每条路径的黑色结点的个数没有变,仍然是1,但是此时祖父节点变为红,可能祖父的父节点此时也为红,违背性质三,故需要循环向上检查)
分析:
0. 这样处理其实也能理解啊... 此时违背规则三,把p变为黑,可是这样g的左子树凭空多了一个黑色,故把根结点变为红,叔叔变为黑。这样子树的每条路径的黑色结点个数就不变了。
1. 情况一的结果是:将祖父结点当作cur继续向上检查,因此,上方抽象图中,cur也不一定是新增结点,只有当abcde子树为空时,cur为新增结点,否则,cur只是之前情况一处理完之后的祖父结点,由黑变为了红。(这里抽象图的实例情况有很多,可以无限延展下去。有兴趣就画一些。)但是总之,情况一概括了当叔叔结点存在且为红的处理方式。
2. 红黑树和AVL树不同,情况一这里,并不关心p和u是g的左还是右,cur是p的左还是右,只关心叔叔的情况。
情况二: cur为红,p为红,g为黑,u不存在/u存在且为黑
说明:此时把p(父亲结点)直接变为红,左子树的路径中多了一个黑色,此时叔叔结点不存在/存在且为黑,就无法像情况一一样处理了。

以上为情况二的一种... 叔叔存在和叔叔不存在。它们的处理方式是一样的。
实际上我想说的是:
若叔叔不存在,则cur一定为新增结点。(因为g的右子树无黑结点)
若叔叔存在且为黑,则cur一定不是新增结点,一定是某情况一处理完之后的祖父结点。(因为g的右子树有黑结点)那么,这里为什么不可能是情况二处理完之后呢?没错,情况二处理完之后就不需要循环向上了(具体见情况二的结果与作用)
但是,不管叔叔结点存在且为黑还是不存在,处理方式都是一样的!(见下文),下面举例时,我只举出叔叔存在且为黑的抽象图,叔叔不存在的情况就是对应的简化情况,cur为新增的情况,很好理解。
情况2.1:p是g的左,cur是p的左。u不存在/u存在且为黑

单旋+变色:g进行右单旋,p变为黑,g变为红。(此时p是整颗子树的根节点)
情况2.2:p是g的右,cur是p的右。u不存在/u存在且为黑

单旋+变色:g进行左单旋, p变为黑,g变为红。(此时p是整颗子树的根节点)(旋转后的结果图略了,类比情况2.1)
情况2.3:p是g的左,cur是p的右。u不存在/u存在且为黑

此处p是g的左,cur是p的右,应当双旋+变色。此时第一次单旋后,就变为了情况2.1,按照情况2.1处理即可。
左单旋之后,和2.1不同的是p和cur的位置,故你可以先swap(cur, p)。然后按照情况2.1一样处理。也可以不swap,直接右旋+变色,但是此时变色是变cur和g,不同于情况2.1的g和u
双旋+变色:p进行左单旋,g进行右单旋。swap之后变色:p变为黑,g变为红。(不swap直接变色:cur变黑,g变红)
情况2.4:p是g的右,cur是p的左。u不存在/u存在且为黑

双旋+变色:p进行右单旋,g进行左单旋。swap(cur, p)之后,p变黑,g变红。(不swap,cur变黑,g变红)
此处和2.3原理相同,在第一次p进行右单旋之后,就变为了2.2的情况,只是cur和p的位置不同。
总结:
综上,情况二的宗旨为:u不存在/u存在且为黑,此时仅仅像情况一一样变色是做不到的,需要旋转+变色。并且情况二和情况一不同的是:情况一不在乎左右之分。而情况二的p与g关系,cur与p的关系将直接决定如何旋转,这里类似AVL树的旋转,只是没有了平衡因子。
情况二不管2.1 2.2 2.3 2.4 进行旋转+变色后,不需要再将根节点当作cur向上处理,因为情况二在旋转变色后,子树根节点一定为黑,并且也同时消除了连续红色结点。故不需要向上处理。
在红黑树的Insert函数中,最后一个while循环就是处理父节点为红的情况的。
红黑树完整实现:
//
// Created by yangzilong on 2022/11/11.
//
#ifndef STL_RBTREE_H
#define STL_RBTREE_H
#include <utility>
#include <iostream>
#include <vector>
using namespace std;
enum Color
{
RED,
BLACK
};
template< class K, class V >
struct RBTreeNode
{
RBTreeNode(const pair<K, V>& kv)
:_left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv)
{ }
RBTreeNode<K, V>* _left;
RBTreeNode* _right;
RBTreeNode* _parent;
pair<K, V> _kv;
Color _col;
};
template <class K, class V>
class RBTree
{
typedef RBTreeNode<K, V> Node;
public:
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 != nullptr) {
if(kv.first > cur->_kv.first) {
parent = cur;
cur = cur->_right;
}
else if(kv.first < cur->_kv.first) {
parent = cur;
cur = cur->_left;
}
else {
return false;
}
}
cur = new Node(kv);
cur->_col = RED;
if(kv.first > parent->_kv.first) {
parent->_right = cur;
}
else {
parent->_left = cur;
}
cur->_parent = parent;
// 插入新结点成功,且为红色。进行判断
// 进入下方循环后,parent一定为红,则parent一定不是根节点,则parent一定有父亲,有没有兄弟不一定。
while(parent && parent->_col == RED) {
Node* grandfather = parent->_parent;
Node* uncle = nullptr;
if(parent == grandfather->_left)
uncle = grandfather->_right;
else
uncle = grandfather->_left;
// 判断叔叔的情况,决定处理方式
if(uncle && uncle->_col == RED) {
// 叔叔存在且为红
parent->_col = BLACK;
uncle->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = cur->_parent;
}
else {
// 叔叔不存在或者叔叔存在且为黑
if(parent == grandfather->_left && cur == parent->_left) {
// 此时,左左,右单旋+变色
// 先变色也可以
parent->_col = BLACK;
grandfather->_col = RED;
RotateR(grandfather);
}
else if(parent == grandfather->_right && cur == parent->_right) {
// 右右,左单旋
parent->_col = BLACK;
grandfather->_col = RED;
RotateL(grandfather);
}
else if(parent == grandfather->_right && cur == parent->_left) {
// cur为红,parent为红,grandfather为黑。
// 右左双旋。
// RotateR(parent);
// RotateL(grandfather);
// // 记住这里是上黑,下面俩红即可。
// cur->_col = BLACK;
// grandfather->_col = RED;
// 第二种实现方法,即单旋后变为双旋。
RotateR(parent);
std::swap(cur, parent);
parent->_col = BLACK;
grandfather->_col = RED;
RotateL(grandfather);
}
else if(parent == grandfather->_left && cur == parent->_right) {
// RotateL(parent);
// RotateR(grandfather);
// // 记住这里是上黑,下面俩红即可。
// cur->_col = BLACK;
// grandfather->_col = RED;
RotateL(parent);
std::swap(cur, parent);
parent->_col = BLACK;
grandfather->_col = RED;
RotateR(grandfather);
}
break;
}
if(cur == _root) {
cur->_col = BLACK;
}
}
return true;
}
void InOrder()
{
_InOrder(_root);
cout << endl;
}
// 看颜色还是看高度?
// 看颜色,因为高度正确不一定是红黑树
bool IsBalance()
{
if(_root == nullptr)
return true;
if(_root->_col == RED) {
cout << "根节点为红色,错误" << endl;
return false;
}
int baseNum = 0;
return PrevCheck(_root, 0, 0);
}
private:
bool PrevCheck(Node* root, int baseNum, int blackNum)
{
if(root == nullptr)
{
if(baseNum == 0) {
baseNum = blackNum;
return true;
}
else if(blackNum != baseNum){
cout << "某条路径黑色结点数量不同,错误" << endl;
return false;
}
else
return true;
}
if(root->_col == BLACK)
blackNum++;
else {
if((nullptr != root->_left && root->_left->_col == RED) || (nullptr != root->_right && root->_right->_col == RED)) {
cout << "出现连续红色结点,错误" << endl;
return false;
}
}
return PrevCheck(root->_left, baseNum, blackNum)
&& PrevCheck(root->_right, baseNum, blackNum);
}
void _InOrder(Node* root)
{
if (root == nullptr)
{
return;
}
_InOrder(root->_left);
cout << root->_kv.first << ":" << root->_kv.second << endl;
_InOrder(root->_right);
}
private:
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之前,保存原先parent->parent
parent->_parent = subR;
if(parent == _root)
{
_root = subR;
subR->_parent = nullptr;
}
else
{
if(parent == ppNode->_right) {
ppNode->_right = subR;
subR->_parent = ppNode;
}
else {
ppNode->_left = subR;
subR->_parent = ppNode;
}
}
}
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR;
if(subLR)
subLR->_parent = parent;
Node* ppNode = parent->_parent;
parent->_parent = subL;
subL->_right = parent;
if(parent == _root) {
_root = subL;
subL->_parent = nullptr;
}
else {
if(parent == ppNode->_right) {
ppNode->_right = subL;
subL->_parent = ppNode;
}
else {
ppNode->_left = subL;
subL->_parent = ppNode;
}
}
}
private:
RBTreeNode<K, V>* _root = nullptr;
};
依旧没有红黑树的删除。包含红黑树自检代码,就是一个递归判断。红黑树的左旋右旋与AVL树的相同,只是没有了平衡因子。
红黑树与AVL树的比较
红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O($log_2 N$),红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入时旋转的次数, 所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多。
红黑树插入操作详解及性质分析
本文详细介绍了红黑树的概念、性质及其插入操作。红黑树是一种保持近乎平衡的二叉搜索树,通过特定的颜色规则确保最长路径不超过最短路径的两倍。在插入新结点时,新结点被设置为红色,可能导致违反红黑树的性质,特别是当父结点也为红色时。文章分析了不同情况下如何通过旋转和变色来调整树的结构,以符合红黑树的性质。此外,还对比了红黑树与AVL树在性能和实现上的差异。

3211

被折叠的 条评论
为什么被折叠?



