红黑树的插入(算法导论)
红黑树是一棵二叉搜索树,它在每个节点增加了一个存储位来表示节点的颜色。一颗红黑树是满足下面红黑性质的二查搜索树:
1)每个节点或是红色的,或是黑色的
2)根节点是黑色的
3)每个叶节点(NIL)是黑色的
4)如果一个节点是红色的,则它的两个子节点都是黑色的
5)对每个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点。
对一颗有n个内部节点的红黑树,其高度至多为2lg(n+1)。这样可以保证红黑树的查找的时间复杂度控制在O(lgn)。
对每一颗红黑树,我们假如所有的叶子节点都为空,并且都是黑色的。而任何非空节点都是内部节点,并且只能是红色或黑色。
1、红黑树的插入
在红黑树的插入中,默认插入的节点是红色的,因为假如插入的节点为黑色的,则会导致红黑树的黑高不等,若是红色的则不会出现此问题,但是这时有可能出现插入节点的父节点也是红色的,这样就破坏了红黑树的第四条性质。这时就需要对红黑树进行调整。
红黑树的插入总共六种情况,其中三种与另外三种对称。假如要插入的节点为z,则
在此我们主要介绍z的父节点是其祖父节点的左孩子的情况。这种情况下又有三种情况,分别为:
1)z的叔叔节点y是红色的。这时将z的父节点和z的叔叔节点y都修改成黑色的,z的祖父节点A原来一定是黑色的,这时将它修改为红色的,并将新的z节点指向z的祖父节点,然后继续向上进行调整。
图1.1 z的叔叔节点为红色的情况
2)z的叔叔节点是黑色的,并且z是一个右孩子节点。此时,首先将z指针指向其父节点,然后以此时z指向的节点为中心进行左旋,于是将此种情况转化成了Case 3的情形。即z节点是左孩子节点。
图1.2 z的叔叔节点为黑色并且z为右孩子
3)z的叔叔节点是黑色的,并且z是一个左孩子节点。此时首先将z的父节点修改为黑色,然后将其祖父节点修改为红色,然后以z的祖父节点为中心进行右旋。
图1.3 z的叔叔节点为黑色并且z为左孩子
在调整结束后,需要将根节点的颜色重新更改为黑色
关于红黑树的创建,插入以及调整的C代码如下:
#include <stdio.h> #include <stdlib.h> typedef struct Node { int key, color; //color = 0:red, 1:black struct Node *left, *right, *p; }NODE, *TREE; TREE rb_insert_fixup(TREE r, NODE *z) { TREE y; y = NULL; while(z->p!=NULL && z->p->color == 0) { if(z->p == z->p->p->left) //z的父节点是其祖父节点的左孩子, 此种情形有三种情况 { y = z->p->p->right; if(y!=NULL && y->color == 0) //case1: z的叔叔节点为红色 { z->p->color = 1; y->color = 1; z->p->p->color = 0; z = z->p->p; } else { if(z == z->p->right) //case2: z的叔叔节点为黑色,并且z是右孩子 ,此时左旋后变成case3的情形 { z = z->p; r = left_rotate(r, z); } //case3: z的叔叔节点为黑色,并且z是左孩子 z->p->color = 1; z->p->p->color = 0; r = right_rotate(r, z->p->p); } } else //z的父节点是其祖父节点的右孩子,此种情形也有三种情况,并且与上面三种情况对称 { y = z->p->p->left; if(y->color == 0) { z->p->color = 1; y->color = 1; z->p->p->color = 0; z = z->p->p; } else { if(z == z->p->left) { z = z->p; r = right_rotate(r, z); } z->p->color = 1; z->p->p->color = 0; r = left_rotate(r, z->p->p); } } } r->color = 1; return r; } TREE rb_insert(TREE r, NODE *z) { TREE x, y; x = r; y = NULL; while(x != NULL) { y = x; if(z->key < x->key) x = x->left; else x = x->right; } z->p = y; if(y == NULL) { r = z; } else if(z->key < y->key) { y->left = z; } else { y->right = z; } z->left = NULL; z->right = NULL; z->color = 0; r = rb_insert_fixup(r, z); return r; } TREE rb_create(int arr[], int n) { TREE r, tmp; int i; r = NULL; for(i=0; i<n; i++) { tmp = (TREE)malloc(sizeof(NODE)); tmp->key = arr[i]; r = rb_insert(r, tmp); } return r; } void print_pre(TREE r) { if(r == NULL) return ; if(r->color == 0) printf("%d:red ", r->key); else printf("%d:black ", r->key); print_pre(r->left); print_pre(r->right); } void print_mid(TREE r) { if(r == NULL) return ; print_mid(r->left); if(r->color == 0) printf("%d:red ", r->key); else printf("%d:black ", r->key); print_mid(r->right); } int main() { TREE r; int n = 6, arr[6]={41, 38, 31, 12, 19, 8}; //create a rb tree r = rb_create(arr, n); printf("----------先序遍历----------:\n"); print_pre(r); printf("\n\n----------中序遍历----------:\n"); print_mid(r); printf("\n\n----------End line----------\n"); return 0; }
2、旋转
现在我们介绍一下刚才用到的旋转,旋转有两种:左旋和右旋。其左旋和右旋过程分别图下图所示。
图2.1 右旋
右旋的C代码如下:
TREE right_rotate(TREE r, NODE *x) { TREE y = x->left; x->left = y->right; if(y->right != NULL) y->right->p = x; y->p = x->p; if(x->p == NULL) r = y; else if(x->p->left = x) x->p->left = y; else x->p->right = y; y->right = x; x->p = y; return r; }
图2.2 左旋
左旋的C代码如下:
TREE left_rotate(TREE r, NODE *x) { TREE y = x->right; x->right = y->left; if(y->left != NULL) y->left->p = x; y->p = x->p; if(x->p == NULL) r = y; else if(x == x->p->left) x->p->left = y; else x->p->right = y; y->left = x; x->p = y; return r; }