- 红黑树的概念
- 红黑树的插入调整
- 判断是否为红黑树
- 红黑树的性能
一.红黑树的概念
1.红黑树的定义
红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。
2.红黑树的性质
- 每个节点不是红色就是黑色
- 根节点必为黑色
- 如果一个节点的红色,则它的左右节点都是黑色(不能出现连续的红色节点)
- 每条路径上的黑色节点数量相同
- 将空节点视为黑色节点(为了更清晰的表示路径)
只要满足上述条件,一定可以保证最长路径不超过最短路径的两倍。因为每条路径的黑色节点相同,所以在一棵树中,最短路径一定是全黑,最长路径一定是一黑一红,黑红相间。
3.红黑树的结构
#include<iostream>
using namespace std;
namespace zs
{
enum Colour
{
RED,
BLACK
};
template<class K, class V>
struct RBTreeNode
{
RBTreeNode<K, V>* _left;
RBTreeNode<K, V>* _right;
RBTreeNode<K, V>* _parent;
Colour _col;
pair<K, V> _kv;
RBTreeNode(const pair<K, V>& kv)
:_left(nullptr)
,_right(nullptr)
,_parent(nullptr)
,_kv(kv)
,_col(RED)
//默认插入颜色设置为红色,因为默认为RED,可能会破坏规则,但默认为黑色一定会破坏规则
{}
};
template<class K, class V>
class RBTree
{
typedef RBTreeNode<K, V> node;
public:
// .....功能
private:
node* _root = nullptr;
};
}
二.红黑树的插入调整
1.插入节点是根节点(违反了规则,需要调整)
将根节点颜色调整为黑色即可
2.插入节点的双亲节点是黑色
该红黑树不需要继续调整
3.插入节点的双亲是红色(违反了规则,需要调整)
由于插入节点是红色,插入节点的双亲节点是红色,所以必有祖父节点且为黑色。不管cur和parent是怎样的位置关系,grandfather永远为黑色,parent永远为红色,cur永远为红色。
3.1叔叔节点存在且为红色(变色即可)
此种情况的抽象图:
A,B,C,D,E为一颗树
此时,只需要将paren变为黑色,uncle变为黑色,grandfather变为红色,这颗树已经调整为红黑树了,但是是否继续更新,取决于grandfather的parent是否为红色或者grandfather是否为根。
- 如果grandfather的parent是黑色,则停止更新
- 如果grandfather是根节点,则将它变为黑色,停止更新
3.2叔叔节点不存在或者存在且为黑色(旋转+变色)
抽象图:
如果叔叔节点不存在/存在且为黑色,此时仅仅改变节点的颜色,已经无法保证满足红黑树的规则了,所以需要通过旋转来改变树形,然后再改变节点颜色即可。
根据parent和cur的位置不同,可分为四种旋转方式:
左旋:
左右旋:
右旋,右左旋类似。
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 != nullptr)
{
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);
if (parent->_kv.first < kv.first)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
cur->_parent = parent;
//进入红黑树部分
//当parent不存在/parent为黑色,则说明不需要向上更新
while (parent != nullptr && parent->_col == RED)
{
//注意这里的grandfather必不可能为nullptr,因为当parent=RED,cur=RED,其grandfather必为黑
node* grandfather = parent->_parent;
//判读parnet是grandparent的左还是右,为了找uncle
if (parent == grandfather->_left)
{
node* uncle = grandfather->_right;
//情况1:uncle存在且为红
if (uncle != nullptr && uncle->_col == RED)
{
parent->_col = BLACK;
uncle->_col = BLACK;
grandfather->_col = RED;
//因为grandfather=RED,可能会出现连续的RR,所以应该继续更新
cur = grandfather;
parent = cur->_parent;
}
else //情况2+情况3:uncle不存在/存在且为黑,需要旋转+变色
{
//如果cur是parent->left,说明需要右单旋
if (cur == parent->_left)
{
RotateR(grandfather);
//修改颜色
parent->_col = BLACK;
grandfather->_col = RED;
}
else//如果cur是parent->right,说明需要左右双旋
{
RotateL(parent);
RotateR(grandfather);
//更新节点颜色
cur->_col = BLACK;
parent->_col = RED;
grandfather->_col = RED;
}
//旋转后,子树的根为黑色,不会影响上层,退出跟新
break;
}
}
else
{
node* uncle = grandfather->_left;
//情况1:uncle存在且为红
if (uncle != nullptr && uncle->_col == RED)
{
parent->_col = BLACK;
uncle->_col = BLACK;
grandfather->_col = RED;
//因为grandfather=RED,可能会出现连续的RR,所以应该继续更新
cur = grandfather;
parent = cur->_parent;
}
else //情况2+情况3:uncle不存在/存在且为黑,需要旋转+变色
{
//如果cur是parent->right,说明需要左单旋
if (cur == parent->_right)
{
RotateL(grandfather);
//修改颜色
parent->_col = BLACK;
grandfather->_col = RED;
}
else//如果cur是parent->left,说明需要右左双旋
{
RotateR(parent);
RotateL(grandfather);
//更新节点颜色
cur->_col = BLACK;
parent->_col = RED;
grandfather->_col = RED;
}
//旋转后,子树的根为黑色,不会影响上层,退出跟新
break;
}
}
//最后需要把根变为黑色,因为有可能因为parent不存在而停止更新,
}
_root->_col = BLACK;
return true;
}
void RotateL(node* parent)
{
node* subR = parent->_right;
node* subRL = subR->_left;
parent->_right = subRL;
if (subRL != nullptr)
{
subRL->_parent = parent;
}
node* pparent = parent->_parent;
subR->_left = parent;
parent->_parent = subR;
if (pparent != nullptr)
{
if (pparent->_left == parent)
{
pparent->_left = subR;
}
else
{
pparent->_right = subR;
}
subR->_parent = pparent;
}
else
{
_root = subR;
subR->_parent = nullptr;
}
}
void RotateR(node* parent)
{
node* subL = parent->_left;
node* subLR = subL->_right;
parent->_left = subLR;
if (subLR != nullptr)
{
subLR->_parent = parent;
}
node* pparent = parent->_parent;
subL->_right = parent;
parent->_parent = subL;
if (pparent == nullptr)
{
_root = subL;
subL->_parent = nullptr;
}
else
{
if (pparent->_left == parent)
{
pparent->_left = subL;
}
else
{
pparent->_right = subL;
}
subL->_parent = pparent;
}
}
三.判断是否为红黑树
红黑树的检测分为两步:
- 检测其是否满足二叉搜索树(中序遍历是否为有序序列)
- 检测其是否满足红黑树的性质
bool isBalance()
{
if (_root == nullptr)
{
return true;
}
//如果根存在且根为红色,则不是RBT
if (_root && _root->_col == RED)
{
cout << "根不是黑色" << endl;
return false;
}
//检查红色节点的子节点是黑色,同时检查每条路径的黑色节点是否相同
int baseValue = -1;
return _check(_root, 0, baseValue);
}
bool _check(node* root, int blackNum, int& baseValue)
{
if (root == nullptr)
{
if (baseValue == -1)
{
baseValue = blackNum;
}
else
{
if (baseValue != blackNum)
{
cout << "某条路径的黑色节点数量不同" << endl;
return false;
}
}
return true;
}
//如果根为红色说明该节点必有父亲节点
if (root->_col == RED && root->_parent->_col == RED)
{
cout << "出现连续的红色节点" << endl;
return false;
}
if (root->_col == BLACK)
{
blackNum++;
}
return _check(root->_left, blackNum, baseValue)
&& _check(root->_right, blackNum, baseValue);
}
四.红黑树的性能
红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O(log N),红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数, 所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多。
- 红黑树的应用场景:
- C++ STL库 – map/set、mutil_map/mutil_set
- Java 库
- linux内核
- 其他一些库