目录
前言
上一章节中,我们介绍了AVL的一些功能,这节我们主要学习一下红黑树的实现。
红黑树的定义及特性
定义:
红黑树是一种自平衡的二叉搜索树,它通过一系列规则来维持平衡,而不是像AVL树那样追求严格的平衡。红黑树的节点被标记为红色或黑色,并通过以下规则确保树的平衡:
- 每个节点不是红色就是黑色。
- 根节点是黑色的。
- 如果一个节点是红色的,则它的两个子节点必须是黑色的。
- 对于每个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点。
- 每个叶子节点(空节点)都是黑色的。
特性:
- 近似平衡:红黑树不追求严格的平衡,而是通过上述规则确保树的近似平衡。这种平衡机制使得红黑树在最坏情况下也能保证查找、插入和删除操作的时间复杂度为O(log n)。
- 高效操作:与AVL树相比,红黑树在插入和删除操作时可能需要的旋转次数更少,因此在某些情况下可能具有更好的性能。
红黑树的实现
红黑树节点的定义
与AVL树不同的是,红黑树通过节点的颜色来实现近似平衡,为了实现这个效果,我们定义一个枚举变量。
enum Colour
{
RED,
BLACK
};
template<class T>
struct RBTreeNode
{
RBTreeNode<T>* _left;
RBTreeNode<T>* _right;
RBTreeNode<T>* _parent;
T _data;
Colour _col;
RBTreeNode(const T& data)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _data(data)
, _col(RED)
{}
};
在这里,我们最开始让节点的颜色为红色,因为红色相比黑色来说,对祖先的影响较小。因为如果新增节点是黑色就需要调整所有路径的黑色数量,比较麻烦。其他和AVL类似。
Insert的实现
先奉上代码:
template<class K, class V>
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);
cur->_col = RED;
if (parent->_kv.first < kv.first)
{
parent->_right = cur;
cur->_parent = parent;
}
else
{
parent->_left = cur;
cur->_parent = parent;
}
while (parent && parent->_col == RED)
{
Node* grandfather = parent->_parent;
if (parent == grandfather->_left)
{
// g
// p u
// c
Node* uncle = grandfather->_right;
if (uncle && uncle->_col == RED)
{
// 变色
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
// 继续往上更新处理
cur = grandfather;
parent = cur->_parent;
}
else
{
if (cur == parent->_left)
{
// 单旋
// g
// p
// c
RotateR(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else
{
// 双旋
// g
// p
// c
RotateL(parent);
RotateR(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
break;
}
}
else // parent == grandfather->_right
{
// g
// u p
// c
//
Node* uncle = grandfather->_left;
if (uncle && uncle->_col == RED)
{
// 变色
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
// 继续往上处理
cur = grandfather;
}
else
{
if (cur == parent->_right)
{
RotateL(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else
{
// g
// u p
// c
//
RotateR(parent);
RotateL(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
break;
}
}
}
_root->_col = BLACK;
return true;
}
因为
新节点的默认颜色是红色
,因此:如果
其双亲节点的颜色是黑色,没有违反红黑树任何
性质
,则不需要调整;但
当新插入节点的双亲节点颜色为红色时,就违反了性质三不能有连
在一起的红色节点
,此时需要对红黑树分情况来讨论:
情况一
:
cur
为红,
p
为红,
g
为黑,
u
存在且为红
解决方式:将
p,u
改为黑,
g
改为红,然后把
g
当成
cur
,继续向上调整。
当是情况一时,我们只需要让p,u变为黑色,让g变为红色继续向上调整,若g为根节点,那么结束循环后直接将颜色改为黑色,这里可能会疑惑为什么不直接将g节点的颜色改为黑色,因为g这棵树不一定是一棵完整的树,有可能是一棵子树,如果改变它的颜色将会影响到其他树。如下面这种情况:
如果完成一次调整后发现g的祖先是红色,那么将继续进行调整,直到合理为止。
情况二
:
cur
为红,
p
为红,
g
为黑,
u
不存在
/u
存在且为黑
解决方法:
p
为
g
的左孩子,
cur
为
p
的左孩子,则进行右单旋;
p
为
g
的右孩子,
cur
为
p
的右孩子,则进行左单旋。
p
、
g
变色
--p
变黑,
g
变红
u情况的说明:
1.如果u节点不存在,则cur一定是新插入节点,因为如果cur不是新插入节点.
则cur和p一定有一个节点的颜色是黑色,就不满足性质4:每条路径黑色节点个
数相同。
2.如果u节点存在,则其一定是黑色的(因为最开始判断的是u为红色,不成立的话一定为黑色),那么cur节点原来的颜色一定是黑色的,现在看到其是红色的原因是因为cur的子树在调整的过程中将cur节点的颜色由黑色改成红色。
1.如果u节点不存在,则cur一定是新插入节点,因为如果cur不是新插入节点.
则cur和p一定有一个节点的颜色是黑色,就不满足性质4:每条路径黑色节点个
数相同。
2.如果u节点存在,则其一定是黑色的(因为最开始判断的是u为红色,不成立的话一定为黑色),那么cur节点原来的颜色一定是黑色的,现在看到其是红色的原因是因为cur的子树在调整的过程中将cur节点的颜色由黑色改成红色。

情况三
:
cur
为红,
p
为红,
g
为黑,
u
不存在
/u
存在且为黑
解决方法:
p
为
g
的左孩子,
cur
为
p
的右孩子,则针对
p
做左右双旋;
p
为
g
的右孩子,
cur
为
p
的左孩子,则针对
p
做右左双旋。
c为黑,g为红。
这这就实现了红黑树的Insert。
下面是左旋右旋代码:
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
parent->_right = subRL;
subR->_left = parent;
Node* parentParent = parent->_parent;
parent->_parent = subR;
if (subRL)
subRL->_parent = parent;
if (_root == parent)
{
_root = subR;
subR->_parent = nullptr;
}
else
{
if (parentParent->_left == parent)
{
parentParent->_left = subR;
}
else
{
parentParent->_right = subR;
}
subR->_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 (_root == parent)
{
_root = subL;
subL->_parent = nullptr;
}
else
{
if (parentParent->_left == parent)
{
parentParent->_left = subL;
}
else
{
parentParent->_right = subL;
}
subL->_parent = parentParent;
}
}
红黑树与AVL树比较
红黑树和
AVL
树都是高效的平衡二叉树,增删改查的时间复杂度都是
O(log_2 N)
,红黑树不追
求绝对平衡,其只需保证最长路径不超过最短路径的
2
倍,相对而言,降低了插入和旋转的次数,
所以在经常进行增删的结构中性能比
AVL
树更优,而且红黑树实现比较简单,所以实际运用中红
黑树更多。