1.红黑树概念
1.1什么是红黑树
红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或 Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。map和set的底层就是以红黑树为框架,因为红黑树与AVL相比在插入和删除时更有优势。
如下图即为一棵红黑树,通过继续对红黑树性质的学习,我们将红黑树如何实现接近平衡
1.2.红黑树的性质
1. 每个结点不是红色就是黑色
2. 根节点是黑色的
3. 如果一个节点是红色的,则它的两个孩子结点是黑色的 (即不能出现连续的红色节点)
4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点(每条路径的黑色节点数目相等)
为什么满足上面的性质,红黑树就能确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的?
由图推广,最长路径一定是全黑设最短路径长度为h,最长路径一定为一黑一红交错排列。
则:最短路径 * 2 >= 最长路径
即最长路径 <= 2h
这只是符合规则的理论最短和最长,实践中最短和最长不一定存在。
2.红黑树的结构及插入
2.1红黑树的结构
红黑树通过维护规则保持树的相对平衡,当插入节点破坏规则时,将进行变色或旋转操作。在进行变色或旋转操作时,需要找到插入点的父节点。为方便操作这里将红黑树的节点设计为三叉链的结构——分别指向左孩子、右孩子、父亲节点。
节点的定义如下
//节点的颜色
enum Colour
{
RED,
BLACK
};
//模版参数Key和Value
template<class K, class V>
struct RBTreeNode
{
RBTreeNode(const pair<K, V>& kv)
: _pLeft(nullptr), _pRight(nullptr), _pParent(nullptr)
, _kv(kv), _colour(RED)//默认每个节点颜色为红色
{}
RBTreeNode<K, V>* _pLeft;// 该节点的左孩子
RBTreeNode<K, V>* _pRight; // 该节点的右孩子
RBTreeNode<K, V>* _pParent; // 该节点的双亲
pair<K, V> _kv; // 该节点储存的数据
Colour _colour;// 该节点的颜色
};
树的定义如下
template<class K, class V>
class RBTree {
typedef RBTreeNode<K, V> Node;
Node* _root;
public:
RBTree() = default;
~RBTree()
{
Destroy(_root);
_root = nullptr;
}
private:
void Destroy(Node* root)
{
if (root == nullptr)
return;
Destroy(root->_pLeft);
Destroy(root->_pRight);
delete root;
}
};
2.2红黑树的插入操作
我们先来讨论插入的节点应该定义为什么颜色
1.定义为黑色,除非为整棵树的根,否则插入一个黑色节点必定使根到这个节点路径的黑色节点数量+1,则必定违反红黑树性质4——每个结点到其所有后代叶结点的简单路径上,不包含相同数目的黑色结点。
2.定义为红色,插入一个红色节点,若其父节点是红色的则会违反红黑树性质2,出现了连续的红色节点。
然而综合两种情况,插入黑色节点时必定违反规则(除根外),插入红色节点时可能违反规则,我们优先选择风险更小的那种插入方式将插入节点默认为红色,然后在违反规则时特殊处理。
红黑树是在二叉搜索树的基础上加上其平衡限制条件,因此红黑树的插入可分为两步:
1. 按照二叉搜索树的规则插入新节点
2.检测新节点插入后,红黑树的性质是否遭到破坏
因为新节点的默认颜色是红色,因此:如果其双亲节点的颜色是黑色,没有违反红黑树任何性质,则不需要调整;但当新插入节点的双亲节点颜色为红色时,就违 反了性质三不能有连 在一起的红色节点,此时需要对红黑树分情况来讨论:
下文中,我们令 cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点(g:grandfather、p:parent、u:uncle)
而插入时双亲节点为红时又存在以下两种情况,需要特殊处理
情况一:cur为红,p为红,g为黑,u存在且为红
图中三角形节点a、b、c、d、e为抽象的树,可以为空或一棵子树,但他们的存在保证符合性质4
这种情况有以下几种具体情况
1.a、b、c、d、e都为空
2.a、b、c、d、e不为空但每条路径黑色节点数目相等
3.cur原本为黑,a、b的插入使cur变红
这样的情况,对子树的调整方法:p、u变黑,g变红,这样的调整方法因为g变红要继续向祖先调整(不确定g的双亲节点的颜色)
这种情况变色的调整方法使g的黑色向g的孩子转移,没有增加每条路径黑色节点的数目
如下
情况二:cur为红,p为红,g为黑,u不存在/u存在但为黑
u存在且为黑色的情况a、b、c路径至少有一个黑色节点
这时我们用变色处理不了问题了,必须要旋转处理
而旋转也分为两种情况
二.1.纯粹的一边高的情况,如图
调整方法: 单次旋转,p变黑,g变红
这种调整会使根到cur的孩子所有路径高度降低(使较高路径变短),且维护了规则
下图为右旋(即向右旋转),左旋思想类似
二.2.一边的中间高的情况
如cur在p的右边的情况,如图
这种情况一次左旋解决不了问题,我们可以先旋转一次变成二.1.那样一边高的情况,再旋转调整
调整方法:先在p子树进行左旋,再对g右旋,cur变黑,g变红
下面给出左右旋及插入操作的完整代码
//红黑树的插入操作
void Insert(const pair<K, V>& kv)
{
Node* newnode = new Node(kv);
Node* cur = _root;
Node* parent = nullptr;
//为空树,若为根节点,颜色为黑色
if (_root == nullptr)
{
newnode->_colour = BLACK;
_root = newnode;
return;
}
//找到插入位置
while (cur)
{
if (kv.first == cur->_kv.first)
{
return;
}
else if (kv.first < cur->_kv.first)
{
parent = cur;
cur = cur->_pLeft;
}
else if (kv.first > cur->_kv.first)
{
parent = cur;
cur = cur->_pRight;
}
}
//链接节点
cur = newnode;
cur->_pParent = parent;
if (kv.first < parent->_kv.first)
{
parent->_pLeft = cur;
}
else
{
parent->_pRight = cur;
}
//根据规则进行调整
//使用循环调整到根节点
while (parent)
{
//若p为黑,不继续向上调整
if (parent->_colour == BLACK)
{
break;
}
//若p为红,进行调整
else
{
//p为红则必有parent,且grandpa必为黑,因为根节点为黑
Node* grandpa = parent->_pParent;
Node* uncle = nullptr;
if (parent == grandpa->_pLeft)
{
uncle = grandpa->_pRight;
}
else
{
uncle = grandpa->_pLeft;
}
//情况1:p和u都为红色,将p和u变黑,g变红,并继续向上更新
if (parent->_colour == RED && uncle != nullptr && uncle->_colour == RED)
{
parent->_colour = BLACK;
uncle->_colour = BLACK;
grandpa->_colour = RED;
//若grandpa为根节点,将其变回黑色,退出循环
if (grandpa == _root)
{
grandpa->_colour = BLACK;
break;
}
//迭代进行下一轮循环
cur = grandpa;
parent = cur->_pParent;
}
//情况2:p为红,u为黑,进行旋转后再变色
else if (parent->_colour == RED && (uncle == nullptr || uncle->_colour == BLACK))
{
//先判断p为g的左还是右
if (parent == grandpa->_pLeft)
{
//2.1 单旋的情况,p为g的左孩子,cur为p的左孩子,右单旋
if (cur == parent->_pLeft)
{
RotatoR(grandpa);
//颜色调整
parent->_colour = BLACK;
grandpa->_colour = RED;
}
//2.2 双旋的情况,p为g的左孩子,cur为p的右孩子,先p左单旋,再g右单旋
else
{
RotatoL(parent);
RotatoR(grandpa);
//颜色调整
cur->_colour = BLACK;
grandpa->_colour = RED;
}
}
else
{
//2.1 单旋的情况,p为g的右孩子,cur为p的右孩子,左单旋
if (cur == parent->_pRight)
{
RotatoL(grandpa);
//颜色调整
parent->_colour = BLACK;
grandpa->_colour = RED;
}
//2.2 双旋的情况,p为g的右孩子,cur为p的左孩子,先p右单旋,再g左单旋
else
{
RotatoR(parent);
RotatoL(grandpa);
//颜色调整
cur->_colour = BLACK;
grandpa->_colour = RED;
}
}
//子树根节点为黑,不会影响祖先,退出循环
break;
}
}
}
}
//左单旋
//可以使根到cur的孩子所有路径高度降低,且维护了规则
void RotatoL(Node* parent)
{
Node* subR = parent->_pRight;
Node* subRL = subR->_pLeft;
Node* parentParent = parent->_pParent;
//先链接节点
if (subRL != nullptr)
subRL->_pParent = parent;
parent->_pRight = subRL;
parent->_pParent = subR;
subR->_pLeft = parent;
subR->_pParent = parentParent;
if (parentParent == nullptr)
{
//若parentParent为空则原Parent为整个树的根
_root = subR;
}
else
{
if (parent == parentParent->_pLeft)
{
parentParent->_pLeft = subR;
}
else if (parent == parentParent->_pRight)
{
parentParent->_pRight = subR;
}
}
}
//右单旋,与左旋思想类似
void RotatoR(Node* parent)
{
Node* subL = parent->_pLeft;
Node* subLR = subL->_pRight;
Node* parentParent = parent->_pParent;
if (subLR != nullptr)
subLR->_pParent = parent;
parent->_pLeft = subLR;
parent->_pParent = subL;
subL->_pRight = parent;
subL->_pParent = parentParent;
if (parentParent == nullptr)
{
_root = subL;
}
else
{
if (parent == parentParent->_pLeft)
{
parentParent->_pLeft = subL;
}
else if (parent == parentParent->_pRight)
{
parentParent->_pRight = subL;
}
}
}