红黑树插入时的情况
红黑树是有序树,在插入一个节点时,插入的结点一定会成为叶结点,且插入的结点一定要是红色的。那么,插入时就可能会产生颜色冲突,即插入结点 N 和 它的父节点 P 的颜色都是红色。如果 P 是黑色,没有冲突,直接插入就好。
颜色冲突问题解决
要明确,在红黑树插入节点 N 时,如果出现颜色冲突,一定需要关注它的叔叔节点 U,这时,自然要先找到 N 的祖父节点 GP。
情况分析
冲突时,N 和 P 都是红的,因为树本身原本是一颗红黑树,此时 GP 一定存在且为黑。分析 U 的情况:
—— U 红
———— P 和 U 涂黑,GP涂红,使 N=GP,然后去观察 N 和 N 的父亲 P 的情况,如果颜色冲突就重复这一步骤,如果不冲突,则下面的步骤也不需要进行。
—— U 黑或 U 不存在
———— 这时,N 和 P 都是红色,如果 N 和 P 不同边(N 和 P 一个是左孩子,一个是右孩子),则需要进行第一次的旋转操作(以 P 为中心旋转,旋转之后,N 和 P 同边),旋转之后,N 会在 P 的上面,使 N=P、P=N->parent。这时,将 P 涂黑,GP 涂红,然后以 GP 进行第二次的旋转,使红黑树重新平衡。
解释
U 红时进行的操作,不会改变红黑树根结点到叶结点的路径中黑色节点的个数,在操作过程中,可能不需要进行下一步的操作(旋转),就已经平衡了。
U 黑时进行的操作,此时可能需要以 P 进行旋转(左旋或右旋),第一次的旋转不会改变结点颜色,也不会改变路径中黑节点的个数,它是为了第二次的旋转准备的,因为第二次旋转必须是同边的。当 N 和 P 同边后,会对 P 和 GP 重新着色,着色以后,路径中黑节点的个数发生了变化,以 GP 进行的第二次旋转使得路径中黑色结点的个数恢复,由此可以知道,插入操作最多只会造成两次的旋转操作。
代码
enum COL{RED,BLACK}; //这里表示颜色
typedef int T;
typedef struct RBNode{
T data;
char col;
struct RBNode *left;
struct RBNode *rigth;
struct RBNode *parent;
}RBNode;
typedef struct RBNode* RBTree; //定义RBTree
//表示节点是左孩子还是右孩子
enum DIRE{L,R};
//创建一个结点,传入父节点地址,返回创建结点的指针
RBNode *rbtree_create_rbnode(T data,RBNode *parent){
RBNode *node = malloc(sizeof(RBNode));
if(node != NULL){
node->col = RED; //每次插入的结点都是红色的
node->data = data;
node->left = NULL;
node->right = NULL;
node->parent = parent;
}
return node;
}
/*以 node 为中心进行左旋,ptree是指向根节点的指针
|| ||
node nr
/ \ --> / \
nl nr node r
/ \ / \
l r nl l
需要处理的有node->parent->right(或->left)、nr->left、nr->parent、l->parent、node->right、node->parent
*/
void left_rotation(RBTree *ptree,RBNode *node){
RBNode *right = node->right;
//处理nr->parent
right->parent = node->parent;
//处理node->parent->right(或->left)
if(node->parent==NULL){ //node是根节点,旋转后nr成为根节点
*ptree = right;
}else{ //node不是根节点
if(node == node->parent->left){
node->parent->left = right;
}else{
node->parent->right = right;
}
}
//处理node->right
node->right = right->left;
if(node->right != NULL){
//处理l->parent
node->right->parent = node;
}
//处理nr->left
right->left = node;
//处理node->parent
node->parent = right;
}
/*
|| ||
node nl
/ \ --> / \
nl nr l node
/ \ / \
l r r nr
*/
void right_rotation(RBTree *ptree,RBNode *node){
RBNode *left = node->left;
left->parent = node->parent;
if(left->parent == NULL){
*ptree = left;
}else{
if(node == node->parent->left){
node->parent->left = left;
}else{
node->parent->right = left;
}
}
node->left = left->right;
if(node->left != NULL){
node->left->parent = node;
}
node->parent = left;
left->right = node;
}
//调节红黑树的平衡,ptree为根结点,node为插入时的结点
void rbtree_insert_repair(RBTree *ptree,RBNode *node){
RBNode *parent = node->parent;
//父节点存在且为红色
while(parent != NULL && parent->col == RED){
RBNode *gp = parent->parent;
//node是父左还是父右
int nd = node == parent->left?L:R;
//parent是祖父左还祖父右
int pd = parent == gp->left?L:R;
RBNode *uncle = pd==L?gp->right:gp->left;
//如果叔叔存在且为红
if(uncle!=NULL && uncle->col == RED){
parent->col = BLACK;
uncle->col = BLACK;
gp->col = RED;
node = gp;
parent = node->parent; //调节时总是先利用 uncle(红) 解决 node 和 parent 的颜色冲突,所以 continue
continue;
}
//这里开始,node 没有 uncle,或者 uncle 为黑
if(nd != pd){//不同边 旋转为同边
if(nd == L){
right_rotation(ptree,parent);
}else{
left_rotation(ptree,parent);
}
node = parent;
parent = node->parent;
}
//在这里,由于同边的红红冲突,N 和 P 为红,GP 黑
gp->col = RED;
parent->col = BLACK;
//经过上述处理 同边 以 GP 旋转
if(pd == L){//和父亲同左,在GP左边
right_rotation(ptree,gp);
}else{//和父亲同右,在GP右边
left_rotation(ptree,gp);
}
break;
}
(*ptree)->col = BLACK; //红黑树根节点必须是黑的,上数过程中,可能会将根节点染红
}
//插入结点 并 调节红黑树
int rbtree_insert(RBTree *ptree,T data,int (*compar)(T,T)){
RBTree *proot = ptree;
RBNode *parent = NULL;
while(*ptree != NULL){
parent = *ptree;
int ret = compar(data,(*ptree)->data);
if(ret < 0){
ptree = &(*ptree)->left;
}else if(ret > 0){
ptree = &(*ptree)->right;
}else{ //等于时不插入
return -1;
}
}
*ptree = rbtree_create_rbnode(data,parent);
if(*ptree == NULL){
return -1;
}
rbtree_insert_repair(proot,*ptree);
return 0;
}