红黑树

来源:互联网 发布:js字符串实例 编辑:程序博客网 时间:2024/06/04 19:11
红黑树

由于二叉搜索树不加限制的情况下在处理基本动态集合操作时,可能使得二叉树的高度变得越来越大,效率有可能比线性表执行效率还慢。 红黑树(R-B Tree)是 “平衡” 二叉搜索树的一种方式,即对二叉搜索树的基本动态集合操作进行了限制(限制其任意操作都要尽可能的保持 树的高度 合理化),那么将二叉搜索树限制成什么样呢,看定义。


红黑树的定义:
一棵红黑树是满足下面红黑性质的二叉搜索树:
(1)每个结点或是RED,或是BLACK
(2)根结点是BLACK
(3)每个(NULL)叶子结点为BLACK
(4)如果一个结点是RED,那么其两个子结点都是BLACK
(5)从任意一个结点到其所有后代叶结点的简单路径上,黑高(bh不包括本身)相同


【为了满足定义, 除了在二叉搜索树结构体中的key left right parent 外, 还要有一个 color元素。同时,所以内部结点的孩子为NULL的结点都将指向一个 哨兵(T.NULL),即,这个结点是所有内部结点中叶结点的孩子和根结点的父亲。】








问题一、红黑树为什么能将二叉搜索树限制的比较“好”呢?

由于对二叉搜索树进行上述定义的限制后就是一个名正言顺的红黑树了,这个红黑树具有如下性质,从而使得二叉搜索树不再那么放纵。
(1)在一棵黑高为k的红黑树中,内部结点最少为 2^(k+1) - 1,最多为 2^(2*k+1 ) - 1
【有性质5 可得当这可红黑树的所有结点都为BLACK时(近似于完美二叉树形态),黑高为k但是高度此时最小即h = bh(x) 。 当高度最大时,即红黑相间 (近似于完美二叉树形态),此时 h = 2 * bh(x)。】
(2)一棵有n个内部结点的红黑树的高度至多为2lg(n+1)
【由于性质(1)所以 n >=2^( bh(x) ) - 1, 当 bh(x) = h / 2时,h最大,所以 n >= 2^(h/2)-1
取对数后为 2lg(n+1) 至少为 lg(n+1)/2(待验证)】
(3) 从某结点x 到其后代叶结点的所有简单路径中,最长的一条最多是最短一条的2倍
(4)在一棵有n个结点的红黑树中,RED:BLACK最大为2:1(红黑交替,并且最底层是RED)最小为0 (全为BLACK)
由(2)可以看出,被包装后的二叉搜索树——红黑树,高度最大为2lg(n+1),又因为,二叉搜索树的动态操作时间复杂度(除找第k大数外)都与高度成正比即0(lgn),所以红黑树在执行动态操作时期复杂度上限时刻保持着0(lgn)。

#include <stdio.h>#include <stdlib.h>#include <string.h>#define RED (0)#define BLACK (1)typedef int elementType;struct treeNode {struct treeNode* left;struct treeNode* right;struct treeNode* parent;elementType data;int color;};typedef struct treeNode* Tree;struct treeNode* TN = NULL;  // 哨兵

问题二、如何去“包装”一棵二叉搜索树的动态操作变成红黑树呢?

红黑树的旋转
为了能在进行动态的删除插入等操作维护一棵二叉搜索树趋于红黑树,就要对REDBLACK结点进行相应的调整,其中就用到了红黑树的旋转。旋转分为左旋和右旋,并且这两者其过程基本类似。以左旋为例:



红黑树的旋转必须在不改变树中结点“顺序”的条件下进行,比如 图一 中序遍历为
α X β Y r 所以, 左旋后 的图二中序遍历也必须为 α X β Y r ,但是为了能保持红黑树的性质,要让X 和 Y 交换位置。所以过程应该为:先将 Y的左子树β 成为X的右子树,然后将 X 成为 Y的左子树。 同时在这个过程中也要对X Y 的 父母之间进行变化。
//左旋static Tree Left_Rotate(Tree T, struct treeNode* x){struct treeNode* y = x->right; //将x的右儿子设置成yx->right = y->left;  // 将 Y的左子树β 成为X的右子树if (y->left != NULL) // 对β父母进行调整{y->left->parent = x;}y->parent = x->parent; //由于X变成Y,需要与红黑树的其他部分重新连接if (x->parent == TN){T = y;}else{if (x == x->parent->left) x->parent->left = y;else                      x->parent->right = y;}y->left = x;  //将 X 成为 Y的左子树x->parent = y;//对 x的父母进行调整return T;}//右旋static Tree Right_Rotate(Tree T, struct treeNode* Y){struct treeNode* x = Y->left;   //将Y的左儿子设置成xY->left = x->right; //  将 x的右子树β 成为y的左子树if (x->right != NULL){x->right->parent = Y;}x->parent = Y->parent;//由于X变成Y,需要与红黑树的其他部分重新连接if (Y->parent == NULL){T = x;}else{if (Y->parent->left == Y) Y->parent->left = x;else                     Y->parent->right = x;}x->right = Y;// Y成为x的右子树Y->parent = x;//对 Y的父母进行调整return T;}


执行旋转操作之后,α 深度+1, X 深度 -1, β 深度不变 Y 深度+1 r 深度-1,所以整体的深度保持不变,即树的形态并没有发生很大的变化,这也就说,红黑树的旋转不会破坏红黑树整体的结构,只是对颜色略有调整。

红黑树的插入

插入的主要过程还是二叉搜索树中Insert( struct treeNode*ST, ElementType item)的过程只不过,要给他套上“红黑树”这一层外衣,即对插入后的节点进行调整,使得新的二叉搜索树又恢复到红黑树的状态。

插入:
插入和二插搜索树的插入操作一样,只不过要对新插入的节点color赋值为RED,而不是BLACK。稍后说明。然后调用RB_Insert_Fixup(T, *temp);对二叉树进行调整。
Tree Insert(struct treeNode *T, elementType item){struct treeNode *pNew = NULL;pNew = (struct treeNode *)malloc(sizeof(struct treeNode));pNew->data = item;pNew->left = pNew->parent = pNew->right = TN;pNew->color = RED;if (T == NULL)//空树{T = pNew;}else{struct treeNode **temp = &T, *parent = NULL;while (*temp != NULL){parent = *temp;if ((*temp)->data <= item)   temp = &(*temp)->right;else                        temp = &(*temp)->left;}pNew->parent = parent;*temp = pNew;RB_Insert_Fixup(T, *temp);}return T;}


调整:
设插入RED节点 z ,其父节点为 y , 其叔节点(y的兄弟节点)为u。
这里仅分析 y为 y和u父亲的 左孩子, u 为右孩子情况,对于相反的情况类似。
(1)当y = BLACK时,无论z是y的左孩子还是右孩子, 无论u是RED 还是BLACK 对整体的红黑树没有影响。


(2)当y = RED时,当u = RED时,无论z是y的左孩子还是友孩子,不满足定义4,调整方法:

因为 y和u同时为RED,所以y.p = BLACK .将 y 为 BLACK,u 为 BLACK, y.p 为 RED。
然后z = y.p 然后循环下去 ,直到z 代表的元素为BLACK为止。


(3)当y = RED, u = BLACK时, 并且z是y的一个左孩子。不满足定义4调整如下:
把y = BLACK y.p = RED, 然后对 y.p 进行右旋 Right_Rotate(T, z.p.p);此时,u处在r位置上,旋转后各个节点黑高不变满足定义5.
(4)当y = RED, u = BLACK时, 并且z是y的一个右孩子。不满足定义4调整如下:
先y左旋变成 (3)这个情况,然后执行(3)的过程。


static Tree RB_Insert_Fixup(Tree T, struct treeNode * z){while (z->parent != TN && z->parent->color == RED){if (z->parent == z->parent->parent->left)//y是左儿子u是右儿子{struct treeNode *u = z->parent->parent->right;if (u != NULL && u->color == RED)//注意 检测u是否存在{//情况(2)z->parent->color = BLACK;u->color = BLACK;z->parent->parent->color = RED;z = z->parent->parent;}else{//情况(4)if (z->parent->right == z){T = Left_Rotate(T, z->parent);}//情况(3)z->parent->color = BLACK;z->parent->parent->color = RED;T = Right_Rotate(T, z->parent->parent);}}else//y是右儿子u是左儿子{struct treeNode *u = z->parent->parent->left;if (u != NULL && u->color == RED)//注意 检测u是否存在{z->parent->color = BLACK;u->color = BLACK;z->parent->parent->color = RED;z = z->parent->parent;}else{if (z->parent->left == z)//与//y是左儿子u是右儿子 情况有区别, 当为y左孩子的时候,右旋 {T = Right_Rotate(T, z->parent);//注意不同}z->parent->color = BLACK;z->parent->parent->color = RED;T = Left_Rotate(T, z->parent->parent);//注意不同}}}T->color = BLACK;return T;}


解释为什么插入时元素z.color 要为RED,而不是BLACK?
如果插入为BLACK,那么将必定会破坏定义5,虽然也能调整得到红黑树,但是对于y = RED情况还有y = BLACK的情况都要进行调整,复杂性上升了近一倍,所以选择为RED合理,这里不是说为BLACK不能达到红黑树的目的,而是对于操作太过复杂。


红黑树的删除
与二叉搜索树相似,也有transplant操作和delete操作。
void RB_Transplant(struct treeNode **T, struct treeNode *key, struct treeNode * newkey){if (key->parent == TN)*T = newkey;else if (key->parent->left == key) key->parent->left = newkey;else                               key->parent->right = newkey;newkey->parent = key->parent;   //不需要判断newkey是否为空因为指向一个哨兵简化了操作}struct treeNode* Tree_Minmum(struct treeNode * T){if (T == NULL) return NULL;while (T->left != NULL){T = T->left;}return T;}


设z为要删除的节点,y为要删除或者移动的节点,x为需要提升到原来y位置上的节点。
Y的颜色决定了红黑树的性质是否会改变。如果Y为RED将不会改变。因为:一、树中的黑高没有变化。二、不存在相邻的两个红节点。当y为RED时,子节点必然为BLACK,当z子节点少于2个时,x将代替z的位置,并不能出现两个RED相邻的情况。当有两个子节点时,z的颜色赋给了y所以不会放生冲突。三、Y不可能为根节点,因为为RED。
如果Y为BLACK将破坏红黑树。所以调用过程RB_Delete_Fixup调整。

void RB_Delete(Tree T,struct treeNode* z){struct treeNode* y = z, * x = NULL;int y_orignal_color = y->color;if (z->left == TN){x = z->right;RB_Transplant(&T, z, z->right);}else{if (z->right == TN){x = z->left;RB_Transplant(&T, z, z->left);}else{struct treeNode* y = Tree_Minmum(z->right);y_orignal_color = y->color;x = y->right;if (y != z->right){RB_Transplant(&T, y, y->right);y->right = z->right;y->right->parent = y;}RB_Transplant(&T, z, y);y->left = z->left;y->left->parent = y;y->color = z->color;}}if (y_orignal_color == BLACK)RB_Delete_Fixup(T, x);}


当Y为BLACK时,调整如下:
额外的BLACK:我们从被删节点后来顶替它的那个节点x开始调整,并认为它有额外的一重BLACK。这里额外一重BLACK是什么意思呢,我们不是把红黑树的节点加上除红与黑的另一种颜色,这里只是一种假设,我们认为我们当前指向它,因此空有额外一种BLACK,可以认为它的BLACK是从它的父节Y点被删除后继承给它的,它现在可以容纳两种颜色,如果它原来是RED,那么现在是红+黑,如果原来是BLACK,那么它现在的颜色是黑+黑。有了这重额外的BLACK,原红黑树性质5就能保持不变。现在只要恢复其它性质就可以了,做法还是尽量向根移动和穷举所有可能性。
如果是以下情况,恢复比较简单:
1.当前节点是红+BLACK
解法,直接把当前节点染成BLACK,结束此时红黑树性质全部恢复。
2.当前节点是黑+黑且是根节点, 解法:什么都不做,结束。
如下情况比较复杂:
设当前节点为x,其兄弟节点为w。
1:x节点是黑+黑且w节点为RED(此时父节点和兄弟节点的子节点分为黑)。
解法:把父节点染成RED,把兄弟结点染成BLACK,之后重新进入算法(我们只讨论当前节点是其父节点左孩子时的情况)。此变换后原红黑树性质5不变,而把问题转化为兄弟节点为BLACK的情况(注:变化前,原本就未违反性质5,只是为了把问题转化为兄弟节点为BLACK的情况)。
2:x节点是黑加黑且w是BLACK且w节点的两个子节点全为BLACK。
解法:把当前节点和兄弟节点中抽取一重BLACK追加到父节点上,把父节点当成新的当前节点,重新进入算法。
  


 
3:x节点颜色是黑+黑,w节点是BLACK,兄弟的左子是RED,右子是BLACK。
解法:把兄弟结点染红,兄弟左子节点染黑,之后再在兄弟节点为支点解右旋,之后重新进入算法。此是把当前的情况转化为情况4,而性质5得以保持,
4:x节点颜色是黑-BLACK,它的w节点是BLACK,但是兄弟节点的右子是RED,兄弟节点左子的颜色任意。
解法:把兄弟节点染成当前节点父节点的颜色,把当前节点父节点染成BLACK,兄弟节点右子染成BLACK,之后以当前节点的父节点为支点进行左旋,此时算法结束,红黑树所有性质调整正确。
void RB_Delete_Fixup(Tree T, treeNode * x){while (x != T&&x->color == BLACK){if (x == x->parent->left){struct treeNode*w = x->parent->right;//case 1if (w->color == RED){w->color = BLACK;x->parent->color = RED;Left_Rotate(T, x->parent);w = x->parent->right;}//case  2if (w->left->color == BLACK&&w->right->right->color == BLACK){w->color = RED;x = x->parent;}//case 3else if (w->right->color == BLACK){w->left->color == BLACK;w->color = RED;Right_Rotate(T, w);w = x->parent->right;}//case 4w->color = x->parent->color;x->parent->color = BLACK;Left_Rotate(T, x->parent);x = T;}else {/*......*/}}x->color = BLACK;}


参考资料:《算法导论》 第三版

完整代码:

#include <stdio.h>#include <stdlib.h>#include <string.h>#define RED (0)#define BLACK (1)typedef int elementType;struct treeNode {struct treeNode* left;struct treeNode* right;struct treeNode* parent;elementType data;int color;};typedef struct treeNode* Tree;struct treeNode* TN = NULL;  // 哨兵 //左旋static Tree Left_Rotate(Tree T, struct treeNode* x){struct treeNode* y = x->right; //将x的右儿子设置成yx->right = y->left;  // 将 Y的左子树β 成为X的右子树if (y->left != NULL) // 对β父母进行调整{y->left->parent = x;}y->parent = x->parent; //由于X变成Y,需要与红黑树的其他部分重新连接if (x->parent == TN){T = y;}else{if (x == x->parent->left) x->parent->left = y;else                      x->parent->right = y;}y->left = x;  //将 X 成为 Y的左子树x->parent = y;//对 x的父母进行调整return T;}//右旋static Tree Right_Rotate(Tree T, struct treeNode* Y){struct treeNode* x = Y->left;   //将Y的左儿子设置成xY->left = x->right; //  将 x的右子树β 成为y的左子树if (x->right != NULL){x->right->parent = Y;}x->parent = Y->parent;//由于X变成Y,需要与红黑树的其他部分重新连接if (Y->parent == NULL){T = x;}else{if (Y->parent->left == Y) Y->parent->left = x;else                     Y->parent->right = x;}x->right = Y;// Y成为x的右子树Y->parent = x;//对 Y的父母进行调整return T;}static Tree RB_Insert_Fixup(Tree T, struct treeNode * z){while (z->parent != TN && z->parent->color == RED){if (z->parent == z->parent->parent->left)//y是左儿子u是右儿子{struct treeNode *u = z->parent->parent->right;if (u != NULL && u->color == RED)//注意 检测u是否存在{//情况(2)z->parent->color = BLACK;u->color = BLACK;z->parent->parent->color = RED;z = z->parent->parent;}else{//情况(4)if (z->parent->right == z){T = Left_Rotate(T, z->parent);}//情况(3)z->parent->color = BLACK;z->parent->parent->color = RED;T = Right_Rotate(T, z->parent->parent);}}else//y是右儿子u是左儿子{struct treeNode *u = z->parent->parent->left;if (u != NULL && u->color == RED)//注意 检测u是否存在{z->parent->color = BLACK;u->color = BLACK;z->parent->parent->color = RED;z = z->parent->parent;}else{if (z->parent->left == z)//与//y是左儿子u是右儿子 情况有区别, 当为y左孩子的时候,右旋 {T = Right_Rotate(T, z->parent);//注意不同}z->parent->color = BLACK;z->parent->parent->color = RED;T = Left_Rotate(T, z->parent->parent);//注意不同}}}T->color = BLACK;return T;}Tree Insert(struct treeNode *T, elementType item){struct treeNode *pNew = NULL;pNew = (struct treeNode *)malloc(sizeof(struct treeNode));pNew->data = item;pNew->left = pNew->parent = pNew->right = TN;pNew->color = RED;if (T == NULL)//空树{T = pNew;}else{struct treeNode **temp = &T, *parent = NULL;while (*temp != NULL){parent = *temp;if ((*temp)->data <= item)   temp = &(*temp)->right;else                        temp = &(*temp)->left;}pNew->parent = parent;*temp = pNew;RB_Insert_Fixup(T, *temp);}return T;}//删除void RB_Transplant(struct treeNode **T, struct treeNode *key, struct treeNode * newkey){if (key->parent == TN)*T = newkey;else if (key->parent->left == key) key->parent->left = newkey;else                               key->parent->right = newkey;newkey->parent = key->parent;   //不需要判断newkey是否为空因为指向一个哨兵简化了操作}struct treeNode* Tree_Minmum(struct treeNode * T){if (T == NULL) return NULL;while (T->left != NULL){T = T->left;}return T;}void RB_Delete_Fixup(Tree T, struct treeNode* x);void RB_Delete(Tree T,struct treeNode* z){struct treeNode* y = z, * x = NULL;int y_orignal_color = y->color;if (z->left == TN){x = z->right;RB_Transplant(&T, z, z->right);}else{if (z->right == TN){x = z->left;RB_Transplant(&T, z, z->left);}else{struct treeNode* y = Tree_Minmum(z->right);y_orignal_color = y->color;x = y->right;if (y != z->right){RB_Transplant(&T, y, y->right);y->right = z->right;y->right->parent = y;}RB_Transplant(&T, z, y);y->left = z->left;y->left->parent = y;y->color = z->color;}}if (y_orignal_color == BLACK)RB_Delete_Fixup(T, x);}void RB_Delete_Fixup(Tree T, treeNode * x){while (x != T&&x->color == BLACK){if (x == x->parent->left){struct treeNode*w = x->parent->right;//case 1if (w->color == RED){w->color = BLACK;x->parent->color = RED;Left_Rotate(T, x->parent);w = x->parent->right;}//case  2if (w->left->color == BLACK&&w->right->right->color == BLACK){w->color = RED;x = x->parent;}//case 3else if (w->right->color == BLACK){w->left->color == BLACK;w->color = RED;Right_Rotate(T, w);w = x->parent->right;}//case 4w->color = x->parent->color;x->parent->color = BLACK;Left_Rotate(T, x->parent);x = T;}else {/*......*/}}x->color = BLACK;}






0 0