红黑树是什么
红黑树是一种二叉树。树的结点具有“color”之类的二值属性。
红黑树的性质
红黑树具有一下性质:
1.根结点必须是黑色
2.叶子结点必须是黑色
3.红色结点的两个子节点必须是黑色(红色结点不能相邻)
4.对于任意节点,它到所有叶子结点的路径上具有相同数量的黑结点(3、4两点决定了最长路径的长度最长是最短路径2倍-1,保证了相对平衡)
红黑树的应用场景
1.cfs调度(key是task的vruntime)
2.Epoll中管理io
3.定时器
4.nginx
5.hashmap中冲突的组织(冲突数量小于阈值时用链表,大于阈值用红黑树)
红黑树和AVL树的区别
1.avl是完全平衡的,最长路径最多比最短路径长1,而红黑树最长路径最多是最短路径的2倍左右。所以用来查找时,红黑树可能比avl多1次。
2.avl树删除时,最坏的情况下需要旋转logn次以维持平衡,而红黑树最多需要三次。
综上,在删除操作较多的场景下红黑树统计性能更好。
红黑树的实现
构造一个红黑树
根据需要,value或key可以用宏或模板抽象。本文简单起见固定写法。
结点:
class rbNode {
friend class rbTree;
public:
rbNode(int k, int v) : _key(k), _value(v), _Red(true) {}
rbNode() : _Red(true) {}
private:
int _key;
int _value;
bool _Red;
rbNode* _parent;
rbNode* _left;
rbNode* _right;
};
树:
class rbTree {
public:
void Insert(rbNode* n);
void Delete(rbNode* n);
private:
void RotateLeft(rbNode* n); //调整黑高时使用
void RotateRight(rbNode *n); //调整黑高时使用
void fix_up(rbNode *n); //插入或删除时,如果改变平衡则调用
bool isLeftChild(rbNode *n); //判断当前结点是父节点的左子树还是右子树
rbNode *GetUncle(rbNode *n); //获取指定结点的叔叔结点
private:
rbNode* root;
rbNode* NIL;
};
工具函数:
bool rbTree::isLeftChild(rbNode* n) {
// need check parent in caller
return n->_parent->_left == n;
}
rbNode* rbTree::GetUncle(rbNode* n) {
rbNode *grandpa = n->parent->parent;
return isLeftChild(n->parent) ? grandpa->_right : grandpa->_left;
}
旋转操作
旋转操作涉及到三对连接(6个指针),按一定的顺序设置指针即可。需要注意的是判断父节点是否为root。
上图为右旋示意图,绿色箭头为需要改变的连接
void rbTree::RotateRight(rbNode* n) {
rbNode* prt = n->_parent;
if (prt == root) {
root = n;
n->_parent = NIL;
}
else {
rbNode* grd = prt->_parent;
if (grd->_left == prt) {
grd->_left = n;
}
else {
grd->_right = n;
}
n->_parent = grd;
}
prt->_parent = n;
n->_right->_parent = prt;
prt->_left = n->_right;
n->_right = prt;
}
void rbTree::RotateLeft(rbNode* n) {
rbNode* prt = n->_parent;
if (prt == root) {
root = n;
n->_parent = NIL;
}
else {
rbNode* grd = prt->_parent;
if (isLeftChild(prt)) {
grd->_left = n;
}
else {
grd->_right = n;
}
n->_parent = grd;
}
n->_left->_parent = prt;
prt->_right = n->_left;
n->_right = prt;
prt->_parent = n;
}
插入
步骤:
1.找到插入位置
2.插入结点
3.如果父节点是红色结点,则需要调整
void rbTree::Insert(rbNode* n) {
if (_root == NIL) {
_root = n;
n->Red = false;
return;
}
rbNode *cur = _root, *prt;
while (cur != NIL) {
prt = cur;
if (n->key > cur->key) {
cur = cur->_left;
}
else {
cur = cur->_right;
}
}
if (prt->key > n->key) {
prt->_left = n;
}
else {
prt->_right = n;
}
n->_parent = prt;
if (prt->Red) {
fix_up(n);
}
}
插入的调整有三种情况:
1.叔父结点存在且为红色,这样的话只需要把父节点和叔父结点变为黑色,祖父结点变为红色,并递归调整祖父结点即可。
2.叔父结点存在且为黑色,这需要先将父节点进行旋转(注意判断当前节点、父节点和祖父结点的位置,如果是直线则直接旋转,是折线则先对当前节点旋转成为直线,再旋转父节点),然后将祖父节点和父节点变色。
3.如果叔叔结点不存在,则将父节点旋转,然后把父节点和祖父节点变色即可。
代码(待补充)