红黑树也是在二叉搜索树的基础是上对其性能的一个拓展, 虽然AVL树的性能已经很高了,但是它在调整的时候由于平衡因子出现的多种情况导致的旋转是非常多的,因此在实现的时候也就会变得相当的复杂. 而红黑树虽然也涉及到了旋转,但他相对于AVL树是稍微简单的,AVL树和红黑树的时间复杂度都为O(long N),假设有是一个数据插入到AVL树中,他操作的次数大约为30次,红黑树操作的次数大约为60次,但对与计算机的计算速度来说都是常数级的.
红黑树是在二叉搜索树的基础上加了一个节点的颜色,这里我们用一个枚举变量来定义,STL的库中是用的bool值,true和false用来形容两种状态.
红黑树的性质
一颗完整的红黑树包括了以下的几个性质
- 红黑树的跟结点一定是黑的的
- 如果一个节点是红色那么他的两个孩子一定是黑色的
- 从任意节点开始的每一条路径所包含的黑色结点的数量是相等的
- 定义叶子节点的两个空的孩子节点是黑色的
红黑树的结点的定义
enum Color{
RED,BLACK
};
temolate<class K,class V>
struct RBTreeNode{
RBTreeNode(const pair<K,V>& data = pair<K,V>())
:_left(nullptr)
,_right(nullptr)
,_parent(nullptr)
, _data(data)
, _color(RED)
{}
RBTreeNode<K,V>* _left;
RBTreeNode<K,V>* _right;
RBTreeNode<K,V>* _parent;
pair<K,V> _data;
Color _color;
};
红黑树并不是从根开始的,这里还有一个头结点,头结点的最主要的作用是实现红黑树的迭代器, 头结点的父节点是树的根节点,根节点的父节点是头结点, 头结点的左孩子是数的最左侧的结点,右孩子是数的最右侧的结点. 头结点不存东西,因此可以给一个缺省值
红黑树结点的插入
插入节点的时候和AVL树大致相同,都是先查找到合适的插入位置插入,在从这个节点开始向上循环进行调整,直到符合红黑树的性质就停止,因为定义的结点是红色的所以应该调整的情况如下:
- 当前节点的父亲节点为红色,并且他的叔叔节点为红色,这种情况只需要将他的父亲节点和叔叔节点染成黑色,将祖父节点染成红色然后更新节点继续向上调整
- 当前节点的父亲节点为红色,但他的叔叔节点不存在或者叔叔节点为黑的时候,这个时候需要旋转之后再进行染色,但在这之前还需要判断是否需要进行双旋
双旋
双旋的条件是当前节点, 父亲节点和祖父节点满足左孩子的右孩子或者右孩子的左孩子是需要进行双旋
单旋
单旋的条件是当前节点,父亲节点和祖父节点满足左孩子的左孩子或者右孩子的右孩子时需要进行单旋
代码如下:
bool Insert(const pair<K,V>& data){
//判断是否是在根的位置插入
if (_header->_parent == nullptr){
pNode root = new Node(data);
//修改根的颜色
root->_color = BLACK;
root->_parent = _header;
_header->_parent = root;
_header->_left = root;
_header->_right = root;
return true;
}
//搜索合适的插入位置
pNode cur = _header->_parent;
pNode parent = nullptr;
while (cur){
//查找的元素是按照pair中的K值查找
if (cur->_data.first > data.first){
parent = cur;
cur = cur->_left;
}
else if (cur->_data.first < data.first){
parent = cur;
cur = cur->_right;
}
else
return false;
}
//创建出新节点并且插入新节点
cur = new Node(data);
if (parent->_data.first < data.first)
parent->_right = cur;
else
parent->_left = cur;
//更新节点的双亲
cur->_parent = parent;
//从新节点开始进行调整
while (cur != _header->_parent && parent->_color == RED){
pNode gParent = parent->_parent;
if (gParent->_left == parent){
pNode uncle = gParent->_right;
//A情况,当前节点的叔叔节点存在且为红
if (uncle && uncle->_color == RED){
parent->_color = uncle->_color = BLACK;
gParent->_color = RED;
cur = gParent;
parent=cur->_parent;
}
//B情况, 当前节点的叔叔节点不存在或者为黑
else{
//C情况,cur是gParent的左孩子的右孩子,则要进行双旋
if (cur = parent->_right){
RotateL(parent);
swap(parent, cur);
}
RotateR(gParent);
gParent->_color = RED;
parent->_color = BLACK;
break;
}
}
//对应的右边
else{
pNode uncle = gParent->_left;
if (uncle&&uncle->_color == RED){
parent->_color = uncle->_color = BLACK;
gParent->_color = RED;
cur = gParent;
parnet=cur->_parent;
}
else{
if (parent->_left == cur){
RotateR(parent);
swap(parent, cur);
}
RotateL(gParent);
gParent->_color = RED;
parent->_color = BLACK;
break;
}
}
}
//是哪种更新,都要保持根节点是黑色的
_header->_parent->_color = BLACK;
//更新头结点的左右孩子
_header->_left = LeftMost();
_header->_right = RightMost();
return true;
}
//给头结点获取最左边的结点作为其左孩子
pNode LeftMost(){
pNode cur = _header->_parent;
while (cur&&cur->_left)
cur = cur->_left;
return cur;
}
//给头结点获取最右边的结点作为其右孩子
pNode RightMost(){
pNode cur = _header->_parent;
while (cur&&cur->_right)
cur = cur->_right;
return cur;
}
(左旋与右旋的代码请参考上一篇博客: AVL树)
红黑树的验证
红黑树的验证就是验证是否满足红黑树的几个性质, 主要是第二个和第三个性质,
验证仁义路径黑色节点数量相同时,我们可以先获取一条路径上的黑色节点个数将他记录下来,之后及利用先序遍历,当走到叶子结点的时候判断这一条路径上的黑的叶子节点是否和记录的数据相等就可以, 当然在进行遍历的时候对每个节点就可以进行性质2的判断,只需判断当前结点为红色时他的父节点是否也为红即可
bool isValidRBTree(){
pNode root = _header->_parent;
if (root == nullptr){
cout << "当前树为空树" << endl;
return true;
}
//判断根节点的颜色是否红色
if (root->_color == RED){
cout << "违反性质:树的根必须是黑色" << endl;
return false;
}
//获取任意一条路径上的黑色节点
pNode cur = root;
size_t BlackNum = 0;
while (cur){
if (cur->_color == BLACK)
BlackNum++;
cur = cur->_left;
}
//前序遍历
return _isValidRBTree(root, BlackNum, 0);
}
bool _isValidRBTree(pNode root, size_t BlackNum, size_t curBlackNum){
if (root == nullptr){
if (curBlackNum != BlackNum){
cout << "违反性质:任意路径上的黑色结点的数量不同" << endl;
return false;
}
return true;
}
if (root->_color == BLACK)
++curBlackNum;
//判断是否能有红的连续
if (root->_parent->_color == RED&& root->_color == RED){
cout << "违反性质:不能有红色的结点相连" << endl;
return false;
}
return _isValidRBTree(root->_left, BlackNum, curBlackNum) &&
_isValidRBTree(root->_right, BlackNum, curBlackNum);
}