终于深入的理解并用代码一步一步实现了红黑树。Linux内核的进程调度和内存管理,STL库的map容器的实现都使用了红黑树,这是一种效率很高的二叉搜索树,效率基本上满足log(n),它与AVL树有什么区别呢?
学习了AVL树之后,如果你觉得关于二叉搜索树的学习就结束了,那你就错了,因为,还有一个树,红黑树,红黑树学习完之后,二叉搜索树基本上就告一段落了,还有一些二叉搜索树的变种,如AA-Tree,在这里就不讲了,弄明白红黑树,学习AA树应该也不是太难的事情了。
可能有人要问,有了AVL树,干嘛还要红黑树呢?是啊,AVL树效率是最稳定最高的二叉搜索树,因为AVL是一颗高度平衡的二叉树,任意一个结点的左右子节点高度差不大于1,有人问,有没有高度差为0的,那我想请问你,给你两个点,你给我造出来一个高度差为0的树出来!!!
闲话不多扯,还是说说AVL树和RB-Tree的故事。AVL树是高度平衡,为了达到高度平衡,在建树和删除的时候操作较为复杂,并不是说变化的算法复杂,而是牵扯的结点较多,有时候为了在插入一个结点以后能满足AVL树的性质,整棵树都会跟着旋转变换。插入和删除的效率较低,而RB-Tree就是为了在提高插入删除效率的同时,又不会使查找效率有大的变化而诞生的。红黑树不要求高度平衡,但是其也是一颗泛平衡树,因为从根节点出发,到叶子节点的最长距离不会超过最短距离的二倍,从下面红黑树的性质就可以看出来:
1.Anode is either red or black.
一个结点非黑即红。
2.Theroot is black.
根节点是黑色
3.Allleaves (NIL) are black.
所有叶子节点是黑色
4.Ifa node is red, then both its children are black.
如果一个结点是红色,则它的孩子节点必须是黑色。
5.Everypath from a given node to any of its descendant NIL nodes contains the samenumber of black nodes.The uniform number of blacknodes in the paths from root to leaves is called the black-height of thered–black tree.
黑高相同。
所谓的黑高,就是指从一个节点出发,到叶子节点(NIL)所经历的黑色节点的个数
如下图所示
这里我们引入了一个NIL节点,这个NIL结点的颜色是黑色的,它并不是数据,而是作为类似于标记用的,说明已经到底了…,为什么引入这个NIL节点?是为了简化删除中的调整代码,在删除的调整函数void deleteadjust(Node** root, Node* node)的注释中会有说明。
红黑树的难点在于插入和删除的调整上,插入和删除节点以后都要保证不会破坏上述五点红黑树特性。插入操作比较简单一些,删除操作比较复杂一点,但是,操作的思路还是比较清晰的,下面我们就结合图例还说明插入和删除的各种case,以及对应的操作方法。
插入:
红黑树规定:待插入的结点的颜色都要定义成红色。
插入操作我们分为两部分,一部分是插入,另一部分是插入后的调整。插入操作和前面BST树以及AVL树的插入一样。插入完毕后,有可能遇到以下几种情况:
Case 1:
上图中N是插入的结点,可以看到,N和其父节点P都是红色,而且N的叔结点U也是红色,这就违背了性质4(如果一个结点是红色,则它的孩子节点必须是黑色),调整的操作方法是:将P和U都变为黑色,G变为红色,这样就满足了性质4,也不会违背性质5,完美结束。
Case 2:
N结点的叔结点U如果是黑节点,操作:G改变为红色,P改变为黑色,在P出一次右旋,就OK了。
Case 3:
图3
可以看到,图2和图1有点像,只要对P进行一次左旋,左旋之后就变成了case2的情况,按照case的操作,就OK了。
后面的插入操作的代码就可以照着上图进行理解,因为插入操作比较简单,因此没有过多的说明。本文的重点放在了删除操作上,插入操作还可以结合以下两个链接的内容来理解:
https://en.wikipedia.org/wiki/Red%E2%80%93black_tree
http://www.cnblogs.com/fornever/archive/2011/12/02/2270692.html(可以理解是维基百科的中文翻译,英文好的同学就不必看了)
删除:
下面,我们就重点说说红黑树的删除操作,
和BST以及AVL树的删除操作一样,其实是找到要删除节点的直接后继(右子树的最左子节点)或者直接前驱节点(左子树的最右子节点),然后用直接后继(或者直接前驱,后面我们都以直接后继来描述)的值替换到要删除的结点的值,然后删除直接后继节点。
如果这个直接后继节点是红色,而且孩子节点都是NIL,那就省事了,直接删除就行了,因为删除这个红节点不会破坏任何一个性质。
如果这个直接后继节点N是黑色,而他的右孩子SR是红色,那么SR的两个孩子肯定是NIL,因为SR本身是红色,随意本身不可能跟红色节点,而N的左节点是nil,这样就是的SR不能有黑色节点,否则就会使得N的左右子树的黑高不一样。调整操作是把SR染成黑色,替换掉N节点即可。
上面我们讨论了两种比较简单的情况,那么我们处理一下负责的情况,那就是如果N是一个黑色的结点,而且N的两个孩子都是NIL节点,那么删除了N以后就会破坏平衡,如下图。
删除了N以后,P的左子树的黑高是少1的,这是就需要调平,当然,P的右子节点不一定是黑色的,这里只是画出一种情况进行说明。后面涉及到的各种case,其实就是P和其右子节点颜色各种不同造成的,下面就一一分析。
case 1:
case 2:
case 3:
case 4:
case 5:
case 5的图只画出了P节点右子树,N节点是P的左子树,没有画,请自行脑补即可。
以上删除操作都是讨论的N是P的左子节点,S是P的右子节点的情况,至于N是P右子节点,S是P左子节点的情况,操作一样,只是左旋变右旋,右旋变左旋以及操作的子节点不一样,操作方法一样。
上面五种case就是能遇到的情况。这里说一下为什么增加NIL这个节点,设想一下,如果没有NIL这个节点而是用NULL,那么我们在第一次进行调整的时候就得作为特殊情况考虑,因为NULL是没有color的,然后才能用通用的调整代码,这里我们引入了NIL之后就好多了,NIL本身无数据,但是其有颜色,所以能将第一次调整操作也融入到通用代码中了。
好了,下面我们就把代码贴上:
/*RB-Tree.c*/
#include "RB-Tree.h"
static Node* NIL = NULL;
/*get grandparent*/
Node* grandparent(Node* node)
{
return node->pp->pp;
}
/*get parent*/
Node* parent(Node* node)
{
return node->pp;
}
/*get uncle*/
Node* uncle(Node* node)
{
if (grandparent(node) == NULL) {
return NULL;
}
return (parent(node) == grandparent(node)->plc) ? grandparent(node)->prc : grandparent(node)->plc;
}
Node* createnode(int key,int val)
{
Node* node = (Node*)malloc(sizeof(Node));
node->cnt = 0;
color = RED;
node->color = color;
node->key = key;
node->val = val;
node->plc = NIL;
node->prc = NIL;
node->pp = NIL;
return node;
}
/*左旋转*/
Node* Left_Rotate(Node** root, Node* node)
{
Node* tmp = node->prc;
node->prc = tmp->plc;
if (tmp->plc != NULL) {
tmp->plc->pp = node;
}
tmp->pp = parent(node);
if (parent(node) == NULL) {
*root = tmp;
}
else {
if (parent(node)->plc == node) {
parent(node)->plc = tmp;
}
else {
parent(node)->prc = tmp;
}
}
node->pp = tmp;
tmp->plc = node;
return tmp;
}
/*右旋转*/
Node* Right_Rotate(Node** root, Node* node)
{
Node* tmp = node->plc;
node->plc = tmp->prc;
if (tmp->prc != NULL) {
tmp->prc->pp = node;
}
tmp->pp = node->pp;
if (parent(node) == NULL) {
*root = tmp;
}
else {
if (node->pp->plc = node) {
node->pp->plc = tmp;
}
else {
node->pp->prc = tmp;
}
}
tmp->prc = node;
node->pp = tmp;
return tmp;
}
/*插入过程*/
RESULT _insert_(Node** root, Node* node)
{
Node* cur = *root;
Node* tmp = NULL;
while (cur != NIL)
{
tmp = cur;
if ( node->val > cur->val ) {
cur = cur->prc;
}
else if (node->val < tmp->val) {
cur = cur->plc;
}
else {
cur->cnt++;
return 2;
}
}
node->pp = tmp;
if (node->key < tmp->key) {
tmp->plc = node;
}
else {
tmp->prc = node;
}
return SUCCESS;
}
/*插入节点后的调整函数*/
void insertadjust(Node**root,Node* node)
{
while (parent(node)->color == RED)
{
/*这里分两种情况,叔结点是红色,叔结点不是红色(叔结点是NULL或者叔结点是黑色)*/
/*叔结点是红色*/
if (uncle(node) != NIL && uncle(node)->color == RED) {
parent(node)->color = BLACK;
uncle(node)->color = BLACK;
grandparent(node)->color = RED;
node = grandparent(node);
if (parent(node) == NULL)
{
break;
}
}
/*叔结点是空或者叔结点是黑色*/
else {
if (parent(node) == grandparent(node)->plc) {
if (node == parent(node)->prc) {
/*左旋*/
node = Left_Rotate(root, parent(node))->plc;
}
parent(node)->color = BLACK;
grandparent(node)->color = RED;
/*右旋*/
Right_Rotate(root, grandparent(node));
}
else {
if (node == parent(node)->plc) {
/*右旋*/
node = Right_Rotate(root, parent(node))->prc;
}
parent(node)->color = BLACK;
grandparent(node)->color = RED;
/*左旋*/
Left_Rotate(root, grandparent(node));
}
break;
}
}
}
/*插入节点,并进行必要的调整*/
RESULT insert(Node** root, int key, int val)
{
Node* node = NULL;
if (*root == NULL) {
NIL = (Node*)malloc(sizeof(Node));
*root = (Node*)malloc(sizeof(Node));
NIL->color = BLACK;
(*root)->color = BLACK;
(*root)->plc = NIL;
(*root)->prc = NIL;
(*root)->pp = NULL;
(*root)->key = key;
(*root)->val = val;
(*root)->cnt = 1;
return SUCCESS;
}
else
{
node = createnode(key, val);
if (!node) {
perror("malloc failure!!!\n");
return FAILURE;
}
}
/*插入节点*/
if (2 == _insert_(root, node)) /*返回2说明插入的值已经存在,只需要计数变量+1即可*/ {
return SUCCESS;
}
/*父节点如果是黑色,就不会冲突,不会违背任何性质,如果父节点是红色,则需要调整*/
insertadjust(root, node);
(*root)->color = BLACK;
return SUCCESS;
}
Node* search(Node * root, int key)
{
Node* node = root;
while (node != NULL)
{
if (key > node->key) {
node = node->prc;
}
else if (key < node->key) {
node = node->plc;
}
else
break;
}
return node;
}
void deleteadjust(Node** root, Node* node)
{
/*nil节点的用处体现在此处,刚进入此函数是,node是nil节点,这样才不会报错,不然如果没有nil,而是NULL,
此处肯定是要报错的(node->color),如果不使用nil节点而用null,要想跳过这个错误,只能在这个函数外
先进行一次处理,使得node不在是NULL以后才能进入此函数*/
while (node != *root && node->color == BLACK)
{
/*node节点经过迭代之后,就不能确定其实其父节点的左子节点还是右子节点了,因此分两种情况处理,
两种情况下,旋转方向不同,因此其兄弟节点的左右子节点颜色判断要相反,case 4 和 case 5 体现了这一点*/
if (node == parent(node)->plc) {
Node* Parent = parent(node);
Node* Brother = parent(node)->prc;/*默认取右子树的最左子节点,所以兄弟节点是右孩子*/
if (Brother->color == RED) {
/*case 1:兄弟节点brother红色,父节点P,brother的左右孩子(不为NIL,否则原来就不平衡)一定是黑,
操作:Parent和Brother的颜色互换,在Parent点左旋转,现在P的左子树还是比右子树黑高少1,因此,
将Brother更新一下,在node处重新进行判断调整*/
Brother->color = BLACK;
Parent->color = RED;
Left_Rotate(root,parent(node));
Brother = Parent->prc;
}
else if (Parent->color == BLACK && Brother->color == BLACK &&
Brother->plc->color == BLACK && Brother->prc->color == BLACK) {
/*case 2:Parent节点是黑色,Brother节点以及Brother节点的左右子节点都是黑色,
操作:将Brother节点变成红色,在Parent节点处左旋,相当于原来以P节点(现在是以B节点)为根子节点
的子树黑高整体减少1,用Brother节点代替原来的node节点,重新进行判断调整*/
Brother->color = RED;
Left_Rotate(root,Parent);
node = Brother;
continue;
}
else if (Parent->color == RED &&
Brother->plc->color == BLACK && Brother->prc->color == BLACK) {
/*case 3:Parent是红色,Brother的孩子们都是黑色
操作:将Parent变为黑色,Brother变为红色即可满足性质*/
Parent->color = BLACK;
Brother->color = RED;
node = *root;
}
else if (Brother->color == BLACK && Brother->prc->color == RED) {
/*case 4:Parent为任意色,如果Brother的右子节点是红色,左子节点颜色任意
Brother颜色编程parent的颜色,Parent的颜色变为黑色,Brother右子节点的颜色改为黑色*/
Brother->color = Parent->color;
Parent->color = BLACK;
Brother->prc->color = BLACK;
Left_Rotate(root,Parent);
node = *root;
}
else if (Brother->color == BLACK &&
Brother->plc->color == RED && Brother->prc->color == BLACK) {
/*case 5:Parent颜色任意,Brother颜色是黑色,Brother的左子节点是红色,右孩子是黑色,
操作:B的左子节点变为黑色,B变为红色,然后在B出右旋,处理以后就变成了case 4的情况*/
Brother->plc->color = BLACK;
Brother->color = RED;
Right_Rotate(root,Brother);
Brother = Brother->plc;/*Brother的左子节点代替了原来brother的位置,因此更新Brother的指向*/
/*对于case 5 处理一下之后变成了case 4的情况,在case 4处理跳出*/
}
}
else {
Node* Parent = parent(node);
Node* Brother = parent(node)->plc;/*默认取右子树的最左子节点,所以兄弟节点是右孩子*/
if (Brother->color == RED) {
/*case 1*/
Brother->color = BLACK;
Parent->color = RED;
Right_Rotate(root, parent(node));
Brother = Parent->plc;
}
else if (Parent->color == BLACK && Brother->color == BLACK &&
Brother->plc->color == BLACK && Brother->prc->color == BLACK) {
/*case 2*/
Brother->color = RED;
Right_Rotate(root, Parent);
node = Brother;
continue;
}
else if (Parent->color == RED &&
Brother->plc->color == BLACK && Brother->prc->color == BLACK) {
/*case 3*/
Parent->color = BLACK;
Brother->color = RED;
node = *root;
}
else if (Brother->color == BLACK && Brother->plc->color == RED) {
/*case 4*/
Brother->color = Parent->color;
Parent->color = BLACK;
Brother->plc->color = BLACK;
Right_Rotate(root, Parent);
node = *root;
}
else if (Brother->color == BLACK &&
Brother->prc->color == RED && Brother->plc->color == BLACK) {
/*case 5*/
Brother->prc->color = BLACK;
Brother->color = RED;
Left_Rotate(root, Brother);
Brother = Brother->prc;
}
}
}
node->color = BLACK;
}
void _delete_(Node** root, Node* node)
{
Node* tmp = NULL;/*实际要被删除的点*/
if (node->plc == NIL || node->prc == NIL) {
tmp = node;
}
else {
Node* rightsubtree = node->prc;
Node* minright = rightsubtree;
while (rightsubtree->plc != NIL) {
minright = rightsubtree->plc;
rightsubtree = rightsubtree->plc;
}
tmp = minright;
}
Node* s = NULL;/*顶替实际要被删除的点的子节点*/
if (tmp->plc != NIL) {
s = tmp->plc;
}
else {
s = tmp->prc;
}
s->pp = parent(tmp);
if (tmp->pp == NIL) {
*root = s;
}
else {
if (tmp == parent(tmp)->plc) {
parent(tmp)->plc = s;
}
else {
parent(tmp)->prc = s;
}
}
if (tmp != s) {
node->key = tmp->key;
node->val = tmp->val;
}
/*实际要删除的点的颜色,如果是红色,删除之后不影响,如果是黑色,则需要调整*/
if (BLACK == tmp->color) {
if (s->color == RED) { /*实际被删除的点的子节点如果是红色,直接将其变为黑色,即满足性质*/
s->color = BLACK;
}
else {
deleteadjust(root, s);
}
}
}
/*删除指定key的元素*/
RESULT delete(Node** root, int key)
{
Node* node = search(*root, key);
if (node == NIL)
{
return SUCCESS;
}
/*如果实际删除的点是个黑色的,就需要调整,不然违背红黑树性质*/
_delete_(root,node);
return SUCCESS;
}
测试代码:
/*test.c*/
#include "RB-Tree.h"
int main()
{
Node* root = NULL;
//int elemarry[] = { 16, 12, 18, 10, 14, 17, 20, 9, 11, 13, 15, 8 };
int elemarry[] = { 1, 6, 11, 13, 15, 17, 22, 25,27 };
for (int i = 0; i < sizeof(elemarry) / sizeof(int); i++)
{
if (SUCCESS != insert(&root, elemarry[i], elemarry[i]))
{
perror("insert failure!!!\n");
return FAILURE;
}
}
delete(&root,17);
return 0;
}
以上代码写的比较随意,基本上保证了代码逻辑上没有什么问题,也阐述了红黑树的插入和删除操作,但是存在一些编码风格和小的BUG的风险,等到真正使用的时候再去仔细的修订吧。但是,若您看出了比较明显的BUG,还请指出,以免错误误导了更多人,谢谢!
好了,到此为止关于二叉搜索树的故事就告一段落了,一路走了,我们从BST到AVL,再到RB-Tree,操作越来越复杂,但是其性能也是越来越好,关于其他的一些变种,以后就不介绍了,掌握了这些,将来再去学习那些变种树也会得心应手了。