定义和性质
红黑树是种平衡二叉搜索树,是特化的AVL树,查找和二叉搜索树无异,但在插入和删除时通过调整保持二叉搜索树的平衡,相对于AVL树,牺牲部分平衡性以换取插入/删除操作时少量的旋转操作,整体来说性能要优于AVL树。可在O(logn)内查找、插入和删除,因此应用非常广泛,非常重要。
红黑树必须满足以下性质:
-
每个结点要么是黑色,要么是红色。
-
根结点是黑色。
-
每个叶子结点(NIL)是黑色。
-
每个红结点的两个子结点一定是黑色。(即不能连续红色,红结点定被黑父子相连)
-
任意一结点到每个叶子结点的路径都包含数量相同的黑结点。(即黑高必须相等)
实际上红色结点只是方便调整(旋转和变色)。此外由后两条性质,可证明红黑树高h<=2*logn,先去所有红结点得全满二叉树,h1<=logn,再加上红节点,h2<=logn,因此得证
性能
- 查找代价:红黑树红黑树虽不像AVL严格平衡,但平衡性能还是比BST好。其查找代价基本在O(logN)左右
- 插入代价:插入结点最多只需要2次旋转,虽然变色操作需要O(logN),但变色十分简单代价小
- 删除代价:删除操作代价要比AVL要好,删除一个结点最多只需要3次旋转操作
如果查找、插入、删除频率差不多,那么选择红黑树
应用
红黑树在工程上应用,主要包括两点:
- 利用红黑树顺序(中序遍历)的功能,如找结点的前驱、后继结点
- 利用红黑树快速查找的功能,适合做索引(索引常用红黑树、hash、b+树),如map的实现
适合在内存中,且数量不确定的应用场景,如实际应用有Linux进程调度器、nginx Timer事件管理、epoll事件块的管理、内存管理等
操作
红黑树能自平衡,靠的是旋转和变色的调整:
- 旋转:和颜色无关,三对6个指针(旋转结点父结点和子结点的指向、旋转结点子节点的子节点指向,还包括各结点的父指针)指向要变,左旋逆时针,右旋顺时针
- 左旋:以x为支点,其右子结点y变为其父结点,y的左子结点b变为x的右子结点,x的左子结点a不变,左旋影响旋转结点,并把右子树的结点往左子树移
- 右旋:以y为支点,其左子结点x变为其父结点,x的右子结点b变为y的左子结点,y的右子结点c不变,右旋影响旋转结点,并把左子树的结点往右子树移
- 变色:结点的颜色由红变黑或由黑变红
截图:6条指针分别对应下面代码中的注释
代码:
void rbtree_left_rotate(rbtree *T,rbtree_node *x)
{
rbtree_node *y = x->right;
x->right = y->left;//1
if(y->left!=T->nil)y->left->parent = x;//2
y->parent=x->parent;//3
//4
if(x->parent==T->nil)T->root=y;
else if(x->parent->left==x)x->parent->left=y;
else x->parent->right=y;
y->left=x;//5
x->parent=y;//6
}
右旋只需交换x和y,交换left和right
插入
插入和二叉搜索树也是在末尾插入,不会旋转,而是插入后可能会调整引起旋转,插入共分3种:
- 树为空树:插入结点作为根并设为黑色
- 插入结点key已存在:更新值即可
- 插入结点父结点为黑:直接插入
- 插入结点父结点为红:父不为根则必有黑色祖父,按叔颜色分类调整
void rbtree_insert(rbtree *T, rbtree_node *z) {
rbtree_node *y = T->nil;
rbtree_node *x = T->root;
while (x != T->nil) {
y = x;
if (z->key < x->key) {
x = x->left;
} else if (z->key > x->key) {
x = x->right;
} else { //2 Exist
return ;
}
}
//now x is nil and y point to x's parent
z->parent = y;
if (y == T->nil) {
T->root = z;//1
} else if (z->key < y->key) {//3
y->left = z;
} else {//3
y->right = z;
}
z->left = T->nil;
z->right = T->nil;
z->color = RED;
rbtree_insert_fixup(T, z);//4
}
插入后的调整
为不影响黑高插入结点为红色,且只有在父为红色时才会调整(违反性质4),则祖父必定是黑色的,叔结点有两种(假定父结点是祖父结点的左子树,右子树同理),调整的情况有两类3种:
-
叔是红色:父和叔同祖父换色,黑高两边相同不旋转(无论结点是左还是右孩子)
-
叔是黑色:分两种,因为旋转方向不同:
- 当前结点是右孩子:对父节点左旋,转成下面情况
3. 当前结点是左孩子:父和祖父换色,并对祖父右旋
void rbtree_insert_fixup(rbtree *T, rbtree_node *z) {
while (z->parent->color == RED) { //z ---> RED
if (z->parent == z->parent->parent->left) {
rbtree_node *y = z->parent->parent->right;
if (y->color == RED) {//1
z->parent->color = BLACK;
y->color = BLACK;
z->parent->parent->color = RED;
z = z->parent->parent; //循环向上调整
} else {
if (z == z->parent->right) {//2,情况2后面转成情况3
z = z->parent;
rbtree_left_rotate(T, z);
}
z->parent->color = BLACK;//3
z->parent->parent->color = RED;
rbtree_right_rotate(T, z->parent->parent);
}
}else {
rbtree_node *y = z->parent->parent->left;
if (y->color == RED) {
z->parent->color = BLACK;
y->color = BLACK;
z->parent->parent->color = RED;
z = z->parent->parent; //z --> RED
} else {
if (z == z->parent->left) {
z = z->parent;
rbtree_right_rotate(T, z);
}
z->parent->color = BLACK;
z->parent->parent->color = RED;
rbtree_left_rotate(T, z->parent->parent);
}
}
}
T->root->color = BLACK;
}
删除
删除最麻烦,删除结点是红色,未改变黑高不需调整,删除结点是黑色,则改变黑高需调整。先执行二叉搜索树的删除过程,再调整使得符合红黑树性质,删除有三种情况:
- 删除结点无子结点:直接删除
- 删除结点仅一子结点:用删除结点的孩子替换删除结点
- 删除结点有两子结点:找到删除结点的后继替换删除结点
rbtree_node *rbtree_delete(rbtree *T, rbtree_node *z) {
rbtree_node *y = T->nil;//y是z的后继或z
rbtree_node *x = T->nil;//x是y的孩子或nil
if ((z->left == T->nil) || (z->right == T->nil)) {
y = z;
} else {
y = rbtree_successor(T, z);
}
//2
if (y->left != T->nil) {
x = y->left;
} else if (y->right != T->nil) {
x = y->right;
}
x->parent = y->parent;//包含1和2的情况,修改父指针
if (y->parent == T->nil) {
T->root = x;
} else if (y == y->parent->left) {
y->parent->left = x;
} else {
y->parent->right = x;
}
//用y替换z
if (y != z) {
z->key = y->key;
z->value = y->value;
}
if (y->color == BLACK) {//黑色需调整
rbtree_delete_fixup(T, x);
}
return y;
}
前两种较简单,第三种又分两种子情形:
-
删除结点的后继位于右子树且无左孩子
-
删除结点的后继位于右子树,但不是删除结点的右孩子
rbtree_node *rbtree_successor(rbtree *T, rbtree_node *x) {
rbtree_node *y = x->parent;
if (x->right != T->nil) {
return rbtree_mini(T, x->right);//1x有右子树则找x右子树的最小值
}
while ((y != T->nil) && (x == y->right)) {//2x无右子树则x是左子树的最右边,找到x的后继结点(即x所属子树的最近的根)
x = y;
y = y->parent;
}
return y;
}
删除后的调整
当前结点是父结点的左子树的情况
-
当前结点的兄弟结点是红色,父和兄弟变色且父左旋
-
当前结点的兄弟结点是黑色,而且兄弟结点的两个孩子结点都是黑色,兄弟变色
-
当前结点的兄弟结点是黑色,且该节点左孩子是红色,右孩子是黑色,兄弟及兄弟左子变色并对兄弟右旋,变为最后一种情况
- 当前结点的兄弟结点是黑色,且该结点的右孩子是红色,又可分为4种(该结点左孩子黑和红,父黑和红),父颜色赋值给兄弟且父为黑,兄弟右孩子变色且父左旋
void rbtree_delete_fixup(rbtree *T, rbtree_node *x) {
while ((x != T->root) && (x->color == BLACK)) {
if (x == x->parent->left) {
rbtree_node *w= x->parent->right;
if (w->color == RED) {//1x的兄弟节点w是红色的,交换父叔颜色,并左旋父
w->color = BLACK;
x->parent->color = RED;
rbtree_left_rotate(T, x->parent);
w = x->parent->right;
}
//2x的兄弟节点w是黑色的,并且w的两个子节点都是黑色的,叔变色即可
if ((w->left->color == BLACK) && (w->right->color == BLACK)) {
w->color = RED;
x = x->parent;
} else {
if (w->right->color == BLACK) {//3x的兄弟节点w是黑色的,而且w的左孩子是红色的,w的右孩子是黑色的
w->left->color = BLACK;
w->color = RED;
rbtree_right_rotate(T, w);
w = x->parent->right;
}
w->color = x->parent->color;//4x的兄弟节点w是黑色的,并且w的右孩子是红色的
x->parent->color = BLACK;
w->right->color = BLACK;
rbtree_left_rotate(T, x->parent);
x = T->root;
}
} else {//x是右子树,与上面完全对称
rbtree_node *w = x->parent->left;
if (w->color == RED) {
w->color = BLACK;
x->parent->color = RED;
rbtree_right_rotate(T, x->parent);
w = x->parent->left;
}
if ((w->left->color == BLACK) && (w->right->color == BLACK)) {
w->color = RED;
x = x->parent;
} else {
if (w->left->color == BLACK) {
w->right->color = BLACK;
w->color = RED;
rbtree_left_rotate(T, w);
w = x->parent->left;
}
w->color = x->parent->color;
x->parent->color = BLACK;
w->left->color = BLACK;
rbtree_right_rotate(T, x->parent);
x = T->root;
}
}
}
x->color = BLACK;
}
nginx中红黑树源码分析
后续准备分析nginx中的红黑树的代码实现。。。