一.红黑树底层原理
红黑树的底层可以看作是AVL树的变种。先前我们了解过AVL的模拟实现。avl对整棵树的控制还是非常严格的,因为高度差不能大于2,导致会经常发生旋转。旋转这个过程也会降低效率,所以为了整体的效率衍生出了红黑树。红黑树旋转的没有那么频繁。红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。
红黑树的节点没有平衡因子,由颜色替代,红色和黑色。红黑树的实现是由四条规则实现的
1. 每个结点不是红色就是黑色
2. 根节点是黑色的
3. 如果一个节点是红色的,则它的两个孩子结点是黑色的
4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点
5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)
当满足这四条规则时,这棵树自然而然就是红黑树了。因为每条路径的黑色节点数是相同的,所以不同路径的节点数的差别一定在于红色节点数的不同。同时又因为红色节点不相邻,所以一条路径上最少可以有0个黑色节点,最多可以有黑色节点数个红色节点。这就控制了有一条路径会比其他路径长出俩倍。
二.红黑树的节点结构
其实本质上和avl树的节点没有任何不同,就是把平衡因子去点换成颜色
enum Colour
{
RED,
BLACK
};
template <class T>
struct RBTreeNode
{
RBTreeNode(const T& data = T())
: _pLeft(nullptr)
, _pRight(nullptr)
, _pParent(nullptr)
, _data(data)
{
}
RBTreeNode<T>* _pLeft;
RBTreeNode<T>* _pRight;
RBTreeNode<T>* _pParent;
T _data;
Colour _col;
};
三.红黑树的插入
由于节点都有颜色所以我们在插入新的节点的时候首先要确定新节点是什么颜色的。这里我们采取插入节点为红色的策略,因为插入红色节点相对简单些。当我们插入一个新节点后,我们需要判断当前树是否还满足规则。若当前根节点为空的话可以直接让当前节点作为根节点。若不为空的话则和avl树一样先找到应该插入的位置,构建插入节点并连接指针的指向。唯一的区别是处理颜色和是否需要旋转的条件
先说parent在grand左边的情况。此时包含三种情形
第一种:uncle存在为红。这种情况不需要进行旋转,只需要进行变色。假设新增节点是D,此时不满足红黑树条件,uncle节点(也就是C)存在且是红色,此时我们让父节点(也就是D)和uncle节点变黑,grandparent(A)变红。不断向上传递颜色直到到根节点位置。最后把根节点变黑。
变色到最后是这个样子
第二种:当uncle节点不存在或者uncle为黑色。且新增节点在parent左侧的时候。需要进行变色加单旋,假设新增节点是D,此时相当于新增在较高左子树的左边,我们需要进行一个右单旋
旋转完成后在进行变色,把parent变黑(parent有可能成为根节点)把grand变红
这样就满足红黑树的条件了
第三种:当uncle节点不存在或者uncle为黑色。且新增节点在parent右侧的时候。需要进行一个双旋。新增节点为e
我们可以先对D进行一个左单旋,此时变成了和情况二一样的情形,再对B进行一个右单旋
最后把新增节点变黑grand节点变红
就满足红黑树条件了
上述三种情况是parent在grand的左测情况,同理还有parent在grand右侧的情况。旋转方式相反思路相同。
pair<iterator, bool> Insert(const T& data)
{
KeyOfT kot;
if (_root == nullptr) //处理当树为空时
{
_root = new Node(data);
_root->_col = BLACK;
return make_pair(iterator(_root),true);
}
Node* parent = nullptr;
Node* cur = _root;
//寻找插入位置
while (cur)
{
if (kot(cur->_data) < kot(data))
{
parent = cur;
cur = cur->_pRight;
}
else if (kot(cur->_data) > kot(data))
{
parent = cur;
cur = cur->_pLeft;
}
else
{
return make_pair(iterator(cur), false);
}
}
cur = new Node(data); //找到位置插入节点
cur->_col = RED; //新插入的节点置为红色
Node* newnode = cur;
cur->_pParent = parent; //调整新插入节点的两个指针指向。
if (kot(parent->_data) < kot(data))
{
parent->_pRight = cur;
}
else if (kot(parent->_data) > kot(data))
{
parent->_pLeft = cur;
}
while (parent && parent->_col == RED)
{
Node* grandfater = parent->_pParent; //保存祖父节点
if (parent == grandfater->_pLeft) //当前节点是祖父的左节点
{
Node* uncle = grandfater->_pRight; //说明叔叔节点数祖父的右节点
//uncle存在且为红色,继续向上变色
if (uncle && uncle->_col == RED)
{
uncle->_col = BLACK; //父亲和叔叔变黑,爷爷变红
parent->_col = BLACK;
grandfater->_col = RED;
cur = grandfater; //继续向上传递
parent = cur->_pParent;
}
//uncle不存在+uncle存在且为黑色 (需要进行变色+旋转,若遇到折线情况需要进行双旋)
else
{
if (cur == parent->_pLeft) //相当于新增节点在较高左子树的左边,右单旋即可
{
RotateR(grandfater);
grandfater->_col = RED;
parent->_col = BLACK;
}
else //相当于新增节点在较高左子树的右侧,形成折线需要进行双旋转,先左旋在右旋
{
RotateL(parent);
RotateR(grandfater);
grandfater->_col = RED;
cur->_col = BLACK;
}
break;
}
}
else if (parent == grandfater->_pRight)
{
Node* uncle = grandfater->_pLeft;
//uncle存在且为红色,继续向上变色
if (uncle && uncle->_col == RED)
{
uncle->_col = BLACK; //父亲和叔叔变黑,爷爷变红
parent->_col = BLACK;
grandfater->_col = RED;
cur = grandfater;
parent = cur->_pParent;
}
//uncle不存在+uncle存在且为黑色 (需要进行变色+旋转,若遇到折线情况需要进行双旋)
else
{
if (cur == parent->_pRight)
{
RotateL(grandfater);
grandfater->_col = RED;
parent->_col = BLACK;
}
else
{
RotateR(parent); //当前情况相当于新增节点在较高右子树的左侧
RotateL(grandfater);
grandfater->_col = RED;
cur->_col = BLACK;
}
break;
}
}
}
_root->_col = BLACK; //最后把根节点的颜色赋值成黑色
return make_pair(iterator(newnode), true); //不能直接使用cur,cur有可能已经被更改
}