目录
一、红黑树的概念
红黑树是⼀棵自平衡 ⼆叉搜索树,他的每个结点增加⼀个 存储位 来表⽰ 结点的颜色 ,可以是红色或者黑色。
与 AVL 树追求"严格平衡"不同,红黑树 允许一定程度的不平衡 。AVL 树通过"平衡因子"维持平衡,而红黑树通过给节点 着色(红色或黑色)来确保高效性。
在保证增删改查效率没有太大影响的前提下,显著减少了平衡调整(旋转)的次数,从而提升总体效率。
红黑树的性质如下:
- 节点颜色 :每个节点要么是红色 ,要么是黑色。
- 根节点 :根节点必须是黑色。
- 叶子节点 :所有叶子节点(即 NULL 节点/空指针)均为黑色。
- 红色约束 :如果一个节点是红色,则其子节点必须是黑色。路径上不能有连续的红色节点。**
- 黑色高度 :从根节点到任意 NULL 节点的所有路径上,黑色节点的数量都相同。
红黑树最长路径不超过最短路径的两倍


二、红黑树节点的定义
红黑树节点定义如下,采用三叉链结构,并添加一个变量表示节点颜色。
cpp
enum Colour
{
RED,
BLACK
};
template<class K, class V>
struct RBTreeNode
{
//三叉链
RBTreeNode<K, V>* _left;
RBTreeNode<K, V>* _right;
RBTreeNode<K, V>* _parent;
//存储的键值对
pair<K, V> _kv;
//结点的颜色
int _col; //红/黑
//构造函数
RBTreeNode(const pair<K, V>& kv)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _kv(kv)
, _col(RED)
{}
};
三、红黑树的插入
红黑树插入步骤分为3步:
1.按照二叉搜索树规则,查找插入位置。
2.将待插入节点插入到树中。
3.若插入节点的父节点是红色的,则要对红黑树进行调整。
tip:新插入节点设置为红色,如果设置成黑色则会导致当前路径比其他路径多一个黑色节点,破坏红黑树特性。
插入之后的平衡调整
插入完成后,并不是会一定对红黑树调整
若插入之后的父节点是黑色,则不用对红黑树调整。如果插入前是空树,则新插入元素变为根,并将其设置为黑色。
若插入之后的父节点是红色,则要对红黑树进行调整,因为父节点是红色的,所以父节点不是根节点(根节点是黑色的),一定有组父节点。
场景1:只变色

当parent和uncle都是红色,grandfather是黑色时,新插入节点会导致出现两个连续红色节点,触发变色,parent和uncle变成黑色,grandfather变成红色,这样就满足了红黑树的性质。
grandfather变红后可能还会再次触发两个连续红节点,再次进行递归向上变色,直到根节点或者无连续的红节点停止。

场景2:旋转+变色
要调整的情况,插入时,parent一定为红,grandfather一定为黑,唯一变量是uncle。
uncle还可能是黑色或空节点。
分类讨论之前,我们首先需要明确需要进行平衡调整的情况下 ,
uncle和cur的关系。一个节点被标记为
cur(确定插入位置之后),仅有两种情况:
- 它是新增节点;
- 它是场景 1 向上调整时标记的节点。
当
uncle不存在时,cur一定是新增节点。 原因:若cur不是新增节点,则其必然是向上调整时标记的节点,那么一定发生了变色。也就是说cur所在的某条路径 A 上至少有两个黑色节点(包括一个根节点)。而uncle不存在,则从根节点到uncle(NULL)位置的路径上的黑色节点数一定少于路径 A,两条路径黑色节点数量不一致,不满足红黑树性质。当
uncle为黑时,cur一定是向上调整时标记的节点。 原因:uncle为黑,则从根节点到uncle的路径 B 上至少有两个黑色节点。如果cur是新增节点,那么从根节点到cur的路径上的黑色节点数一定少于路径 B,两条路径黑色节点数量不一致,不满足红黑树性质。


cucle不存在:

这种情况下,路径黑色节点个数不一样,只进行变色不能满足红黑树的性质,这时需要配合旋转来解决问题。
单旋+变色

我们以grandfather为旋转点,进行单旋,**然后将parent变黑,grandfather变红,**整个结构满足红黑树性质,并且该部分的根已经变成黑色,无需继续向上调整,插入结束。
双旋+变色

双旋完成后,**grandfather变红,cur变黑,**整个结构满足红黑树性质,并且该部分的根已经变成黑色,无需继续向上调整,插入结束。
cucle存在且为黑:
当 uncle 为存在且为黑时,cur 一定是向上调整时标记的节点。

a,b,c,d,e表示路径黑节点个数,第一步到第二部是发生了变色,这时叔叔是黑色,就需要先旋转再变色了,**单旋完成后要将 parent 变黑,grandfather 变红;双旋完成后要将 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 (kv.first < cur->_kv.first)
{
parent = cur;
cur = cur->_left;
}
else if (kv.first > cur->_kv.first)
{
parent = cur;
cur = cur->_right;
}
else
{
return false;
}
}
cur = new Node(kv);
cur->_col = RED;//插入红色节点
if (kv.first < parent->_kv.first)
{
parent->_left = cur;
}
else
{
parent->_right = cur;
}
cur->_parent = parent;
//parent为红,进行平衡调整
while (parent && parent->_col == RED)
{
Node* grandfather = parent->_parent;
if (parent == grandfather->_left)
{
Node* uncle = grandfather->_right;
if (uncle && uncle->_col == RED)//uncle为红,仅变色
{
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
//继续向上判断
cur = grandfather;
parent = cur->_parent;
}
else //uncle为黑或不存在,旋转+变色
{
if (cur == parent->_left)//右单旋
{
RotateR(grandfather);
//变色
parent->_col = BLACK;
grandfather->_col = RED;
}
else //左右双旋
{
RotateL(parent);
RotateR(grandfather);
//变色
cur->_col = BLACK;
grandfather->_col = RED;
}
break;
}
}
else
{
//确定uncle
Node* uncle = grandfather->_left;
if (uncle && uncle->_col == RED)//uncle为红,仅变色
{
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
//继续向上判断
cur = grandfather;
parent = cur->_parent;
}
else//uncle为黑或不存在,旋转+变色
{
if (cur == parent->_right)//左单旋
{
RotateL(grandfather);
//变色
parent->_col = BLACK;
grandfather->_col = RED;
}
else// 右左双旋
{
RotateR(parent);
RotateL(grandfather);
//变色
cur->_col = BLACK;
grandfather->_col = RED;
}
break;
}
}
}
//最后将根节点设置为黑色
_root->_col = BLACK;
return true;
}
//右单旋
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR;
if (subLR) subLR->_parent = parent;
Node* ppNode = parent->_parent;
subL->_right = parent;
parent->_parent = subL;
if (parent == _root)
{
_root = subL;
subL->_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;
Node* ppNode = parent->_parent;
subR->_left = parent;
parent->_parent = subR;
if (parent == _root)
{
_root = subR;
subR->_parent = nullptr;
}
else
{
if (ppNode->_left == parent) ppNode->_left = subR;
else ppNode->_right = subR;
subR->_parent = ppNode;
}
}
四、红黑树的查询
红黑树的查询的实现和普通二叉搜索树一样
cpp
//查找
Node* Find(const K& key)
{
Node* cur = _root;
while (cur)
{
if (key < cur->_kv.first)
{
cur = cur->_left;
}
else if (key > cur->_kv.first)
{
cur = cur->_right;
}
else
{
return cur;
}
}
return nullptr;
}
五、红黑树的验证
红黑树是特殊的二叉搜索树,可以用中序遍历来判断是否符合二叉树的性质。
cpp
bool IsBalance()
{
if (_root && _root->_col == RED)
{
cout << "根节点颜色是红色" << endl;
return false;
}
int benchmark = 0;//找到一条路径作为基准值 然后看看其他路径是否相等
Node* cur = _root;
while (cur)
{
if (cur->_col == BLACK)
++benchmark;
cur = cur->_left;
}
// 连续红色节点
return _Check(_root, 0, benchmark);
}
bool _Check(Node* root, int blackNum, int benchmark)
{
if (root == nullptr)
{
if (benchmark != blackNum)
{
cout << "某条路径黑色节点的数量不相等" << endl;
return false;
}
return true;
}
if (root->_col == BLACK)
{
++blackNum;
}
if (root->_col == RED
&& root->_parent
&& root->_parent->_col == RED)
{
cout << "存在连续的红色节点" << endl;
return false;
}
return _Check(root->_left, blackNum, benchmark)
&& _Check(root->_right, blackNum, benchmark);
}
六、红黑树和AVL树对比
红黑树 (RB-Tree) 与 AVL 树同为增删查改时间复杂度达 O(logN) 的自平衡二叉查找树
- AVL 树通过严格控制左右子树高度差绝对值不超过 1 来维持极度扁平的结构,具备最优的查询效率,但在执行删除等修改操作时需回溯维护整条路径的平衡,引发高达 O(logN) 量级的旋转,仅适用于读多写少的静态场景;
- 红黑树则通过节点颜色约束实现"最长可能路径不超过最短可能路径 2 倍"的近似平衡,以略微牺牲查询性能为代价,将插入与删除引发的旋转次数严格收敛于 O(1) 量级(分别最多 2 次和 3 次)。
- 总体来说RB的整体性能高于AVL,因此在实际应用中基本上都是用的RB