红黑树的研究

来源:互联网 发布:淘宝 电脑版 编辑:程序博客网 时间:2024/05/22 11:38

1 概念、原理


1.1  什么是红黑树,相对于平衡二叉树(AVL)有什么优点

红黑树(Red Black Tree):是一颗二叉搜索树,它在每个结点上增加了一个存储位来表示结点的颜色,可以是RED或BLACK。通过对任何一条从根到叶子的简单路径上各个结点的颜色进行约束,红黑树确保没有一条路径会比其他路径长出2倍,因为是近似平衡的。

树中每个结点包含5个属性:color、key、left、right和p。如果一个结点没有子结点或父结点,则该结点相应指针属性的值为NIL。

红黑树和AVL树类似,都是在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能。自从红黑树出来后,AVL树就被放到了博物馆里,据说是红黑树有更好的效率,更高的统计性能。红黑树和AVL树的区别在于它使用颜色来标识结点的高度,它所追求的是局部平衡而不是AVL树中的非常严格的平衡。


1.2  红黑树的规则:

  1. 根就是黑结点
  2. 一个结点不是红就是黑
  3. 每个叶子结点(NIL)是黑的
  4. 如果一个结点是红色的,则它的两个子结点都是黑色的。(即不存在两个连续的红色结点)
  5. 任意一个结点,到他的所有叶子结点的所有路径的黑高度(即黑色结点数)都是一样的。
  6. 每个叶子结点都带有两个空的黑色结点(被称为黑哨兵),如果一个结点n的只有一个左孩子,那么n的右孩子是一个黑哨兵;如果结点n只有一个右孩子,那么n的左孩子是一个黑哨兵

红黑树的这6个性质中,第6点是比较难理解的,但它却非常有必要。我们看图1中的左边这张图,如果不使用黑哨兵,它完全满足红黑树性质,结点50到两个叶结点8和叶结点82路径上的黑色结点数都为2个。但如果加入黑哨兵后(如图1右图中的小黑圆点),叶结点的个数变为8个黑哨兵,根结点50到这8个叶结点路径上的黑高度就不一样了,所以它并不是一棵红黑树


1.3 左旋、右旋:

不管是左旋还是右旋都不改变树中左,中,右节点的大小关系(即前后继的关系)。这也是定义左右旋的基本原则,下面是摘自算法导论一节中关于左右旋的定义。左右旋的定义太重要了,然后网上一大把讲红黑树的文章,但缺把这个最重要的两个概念的定义给忽略了,左右旋是有如下的明确定义的,并且他为什么这么定义,是很有讲究的,下面说的很清楚,这个选择可以改变树的结构,但不会改变二叉树的性质,这一点很重要。有了这个性质,就决定了这个旋转是唯一合法的。


左旋涉及到的结点的指针操作有三处(见如下图),如果我们把结点之间的连接表示为一条边的话,那这个旋转就涉及到3条边(如下图标注的1,2,3),而每条边都对应两个指针,即一个是从父指向儿子{左儿子(z.p->left)或右儿子(z.p->right)},一个从儿子指向父(z.p)


左旋对应的代码(下面代码中的step1,step2,step3对应修改上面图中的边1,边2,边3):
bool RBTree::RB_left_rotate(RBTree & T, Node & z){Node * y = z.right;z.right = y->left;//step1if(y->left != T.nil)        //step1y->left->p = &z;//step1y->p = z.p;//step2if(z.p == T.nil){T.Root = y;}else if(z.p->left == &z){z.p->left = y;}else  //z.p->right == z{z.p->right =y;}//step2y->left = &z;//step3z.p = y;//step3return true;}
右旋此处省略。详细的就参考下面的代码。

2 红黑树的插入


2.1 插入规则

插入规则规定,刚插入的新结点都是红色的,这样就保证了,新插入的结点不破外树的黑高度(即红黑树的性质5得以保持),但可能会导致出现两个连续的红色结点(破外性质4)。所以当新插入的节点跟他对应的父节点都是红色时(z和z.p的color都是RED时),需要做些旋转和着色来保持树的红黑性质。
分三种情况(z是新插入的结点,且是红色的;z.p是z的父节点,且也是红色的;y是z的叔(z.p.p.right or z.p.p.left)):
  1. z的叔结点y是红色的
  2. z的叔结点y是黑色的,且z是一个右孩子
  3. z的叔结点y是黑色的,且z是一个左孩子
关于情况1的处理,见下面(摘自算法导论)

情况2可以通过左旋变成情况3,注意由于这里左旋的A和B结点都是红色,所以该旋转不改变红黑树的性质。
情况3:通过交换B和C的颜色,并且左旋或右旋来达到使树保持红黑的性质




由于刚插入的结点都是红色的,即插入时,保证了树的黑高度不变,但我们知道一颗树从最开始只有一个结点(即黑高速是1),到后面随着结点的不断插入,树的黑高度会增加,那是怎么增加的呢?奥秘就是在红黑树的第一条性质中(根结点是黑结点)。

2.2 红黑树的插入实例


在了解了红黑树的插入规则后,理解和深化这个过程最有效的方法就是自己试着动手画几个数值被插入到红黑树时,应该如何变换这个节点,使其满足红黑树的性质。
例如下面就用算法导论一书中的一个题目来加深理解红黑的这个插入变换过程:将关键字41,38,31,12,19,8连续地插入一颗初始为空的红黑树之后,试画出该结果树。下面即是这个插入的变换过程图:


3红黑树的删除

红黑树本身是一棵二叉查找树,它的删除和二叉查找树的删除类似。首先要找到真正的删除点,当被删除结点n存在左右孩子时,真正的删除点应该是n的中序遍历前驱(或后继),关于这一点请复习二叉查找树的删除。如下图所示,当删除结点20时,实际被删除的结点应该为18,结点20的数据变为18


3.1 删除时一些重要的结论:

  1. 删除操作中真正被删除的必定是只有一个红色孩子或没有孩子的结点
  2. 如果真正的删除点是一个红色结点,那么它必定是一个叶子结点
关于第一点做如下说明:因为正真被删除的节点,一定是所在分支最小或是最大的节点,所以他不可能是带有两个孩子的节点,因为如果是这样的话,这个节点即不是最大的,也不是最小的。所以该节点只能是一个不带孩子或是只带一个孩子的节点。如果带一个孩子,则这个孩子只能是红色的,如果是黑色的,则会出现黑高不一致的情形。
关于第二点的说明:根据第一点的结论,该红色节点如果不是叶子节点,则只能是带一个孩子的红色节点,并且该孩子还只能是黑色的,这样的,就会导致黑高度不一致,所以只能是红色不带孩子的叶子节点。

3.2 红黑树的删除过程:

在红黑树的删除操作中,删除的目标结点标记为z,而真正被删除的则是z的后继y(tree_minimum(z.right)),而x则是y的后继(y.right,为什么不是y.left?因为y已经是所在分支的最小值,如果y存在左分支,则y就不会是最小的,所以y的后继只能是y.right,按升序排列).
删除过程中,将y迁移到z:即在删除结点z前,将z的左右孩子指针(y.left/right=z.left/right)、父指针(y.p=z.p)、颜色(y.color=z.color)拷贝到y,但保留y的key值,并将原来z的左右孩子对应的父指针修改指向为y(z.left.p=y,z.right.p=y), 还需要将将z的父的孩子指针修改指向y(z.p.left/right=y),然后将y的后继x填充到y原来的位置:
  • 当y是黑色时(z.color == BLACK),由于y被删除了,所以会导致红黑树的黑高度减少,所以需要进行变换,让树重新恢复黑高度;此时会将y的黑色虚拟的加到x上面,使x成为一个双重的黑色。但在实际的变换过程中,x可能是双重黑色或是红黑色,这个要特别注意。变换的目的就是将双重黑或是红黑结点中的一重黑在该结点所在的分支变换出一个额外的黑色结点,从而达到保持树的黑高度的目的。
  • 当y是红色时(z.color == RED),由于删除的y并未影响黑高度,并且由于y是红色的,则y的后继x必定是黑色的,所以不会出现连续两个红色,所以此时只需要将x填充到y的位置即可。
删除的基本算法(当y为BLACK时,分四种情况来讨论,详细见下面):


z.color == BLACK时,则需要做些变换来保持红黑树的平衡(分为4中情况,对应上面代码的四种case):

  • case1 x的兄弟结点w(w=x.p.right/left)是红色的

  • case2 x的兄弟结点w是黑色的,而且w的两个子结点都是黑色的(双黑)

  • case3 x的兄弟结点w是黑色的,w的左孩子是红色的,w的右孩子是黑色的

  • case4 x的兄弟结点w是黑色的,且w的右孩子是红色的


4 c++源代码实现

该红黑树的c++实现,是根据《算法导论》中的伪代码实现的。
主要是实现了红黑树的插入、删除操作。RBTree.h文件如下,主要是类的声明
#pragma once#include <iostream>#include <stdlib.h>class RBTree{class Node;private:bool RB_left_rotate(RBTree & T, Node & z);Node * tree_minimum(RBTree & T,Node & z);bool RB_right_rotate(RBTree & T, Node & z);bool RB_transplant(RBTree & T, Node & old_one, Node & new_one);bool RB_insert_fixup(RBTree & T, Node * z);bool RB_delete_fixup(RBTree & T, Node * z);public:enum {BLACK = 0, RED};class Node{public:Node * left;Node * right;Node * p;bool color;int key;//Node & operator=(const Node & z){left = z.left; right = z.right; p = z.p; color = z.color;}Node(Node * L, Node  * R, Node * P, bool C = RED, int key1 = -1 ){left = L; right = R; p = P; color = C; key = key1;std::cout <<"not default contructor function"<<std::endl; }Node(){left = NULL; right = NULL; p = NULL; color = RED; key = -1;std::cout << "default contructor function"<<std::endl;}};Node *Root;Node *nil;// 红黑树的插入bool RB_insert(RBTree & T, Node & z);// 红黑树的删除RBTree::Node * RB_delete(RBTree & T, Node & z);// 红黑树的查找(依赖于遍历)RBTree::Node * RB_search(RBTree & T, int key);// 红黑树的遍历RBTree::Node *  RB_next(RBTree & T);RBTree::Node *  RB_next(const Node * z); RBTree(void);~RBTree(void);};
RBTree.cpp文件如下,主要是类的定义
#include "RBTree.h"#include <string>using std::cout;using std::cin;using std::endl;using std::string;//using namespace Redblack;RBTree::RBTree(void){cout <<"RBTree()\n";nil = new Node(NULL, NULL, NULL, BLACK, -1);Root = nil;}RBTree::~RBTree(void){if(nil){delete nil;nil = NULL;}}//递归实现方法RBTree::Node * RBTree::tree_minimum(RBTree & T,Node & z){if(z.left == T.nil)return &z;else{return tree_minimum(T,*(z.left));}}bool RBTree::RB_left_rotate(RBTree & T, Node & z){Node * y = z.right;z.right = y->left;//step1if(y->left != T.nil)//step1y->left->p = &z;//step1y->p = z.p;//step2if(z.p == T.nil){T.Root = y;}else if(z.p->left == &z){z.p->left = y;}else  //z.p->right == z{z.p->right =y;}//step2y->left = &z;//step3z.p = y;//step3return true;}bool RBTree::RB_right_rotate(RBTree & T, Node & z){Node * y = z.left;z.left = y->right;if(y->right != T.nil)y->right->p = &z;y->p = z.p;if(z.p == T.nil){T.Root = y;}else if(z.p->left == &z){z.p->left = y;}else //z.p->right == &z{z.p->right = y;}y->right = &z;z.p = y;return true;}/*将v迁移到u的位置,case1:u为rootcase2:u为右分支   \      ------->        \    u   v................................case3:u为左分支 /      ------->        /    u   v*/bool RBTree::RB_transplant(RBTree & T, Node & u, Node & v){if(u.p == T.nil) //case1{T.Root = &v;}else if(&u == u.p->right) //case2{u.p->right = &v;}else //case3{u.p->left = &v;}v.p = u.p;return true;}//bool RBTree::RB_insert_fixup(RBTree & T, Node & z)bool RBTree::RB_insert_fixup(RBTree & T, Node * z){Node * uncle;while(z->p->color == RED){if(z->p == z->p->p->left)  //父节点处在左分支{uncle = z->p->p->right;if(uncle->color == RED)//case1{z->p->color = BLACK;//case1uncle->color = BLACK;//case1z->p->p->color = RED;//case1z = z->p->p;//case1}else{if( z == z->p->right )//case2{z = z->p;//case2RB_left_rotate(T, *z);//case2}z->p->color = BLACK;//case3z->p->p->color = RED;//case3RB_right_rotate(T, *(z->p->p));         //case3}}else //父节点处在右分支,跟父亲处在左分支的代码基本相同,除了“左”和“右”互换{uncle = z->p->p->left;if(uncle->color == RED){z->p->color = BLACK;uncle->color = BLACK;z->p->p->color = RED;z = z->p->p;}else{if( z == z->p->left ){z = z->p;RB_right_rotate(T, *z);}z->p->color = BLACK;z->p->p->color = RED;RB_left_rotate(T, *(z->p->p));         }}}T.Root->color = BLACK;return true;}/**/bool RBTree::RB_delete_fixup(RBTree & T, Node * x){Node *w;// w is brother of x node//to be continuedwhile(x != T.Root && x->color == BLACK){if(x == x->p->left)//x 是左分支  {w = x->p->right;if(w->color == RED)                         //case1{w->color = BLACK;//case1x->p->color = RED;//case1RB_left_rotate(T,*(x->p));//case1w = x->p->right;//case1}else //w->color == BLACK{if(w->left->color == BLACK && w->right->color == BLACK)//case2{w->color = RED;//case2x = x->p;//case2}else if(w->right->color == BLACK)//case2{w->left->color = BLACK;//case3w->color = RED;//case3RB_right_rotate(T,*(w));//case3w = x->p->right;//case3}else//case4{w->color = x->p->color;//case4x->p->color = BLACK;//case4w->right->color = BLACK;//case4RB_left_rotate(T,*(x->p));//case4x = T.Root;//case4}}}else ////x 是右分支,情况跟以上一样,除了“左”与“右”互换{w = x->p->left;if(w->color == RED)                         //case1{w->color = BLACK;//case1x->p->color = RED;//case1RB_right_rotate(T,*(x->p));//case1w = x->p->left;//case1}else //w->color == BLACK{if(w->left->color == BLACK && w->right->color == BLACK)//case2{w->color = RED;//case2x = x->p;//case2}else if(w->left->color == BLACK)//case2{w->right->color = BLACK;//case3w->color = RED;//case3RB_left_rotate(T,*(w));//case3w = x->p->left;//case3}else//case4{w->color = x->p->color;//case4x->p->color = BLACK;//case4w->left->color = BLACK;//case4RB_right_rotate(T,*(x->p));//case4x = T.Root;//case4}}}}x->color = BLACK;return true;}bool RBTree::RB_insert(RBTree & T, Node & z){Node * parent = T.nil;Node * son = T.Root;while(son != T.nil)//在二叉树中,找到插入位置{parent = son;if(z.key > son->key)son = son->right;elseson = son->left;}z.p = parent; // parent 指向被插入点的父节点if(parent == T.nil) //如果z是目前tree中的唯一节点,则修改T.RootT.Root = & z;else if (parent->key > z.key)//插入到左分支parent->left = & z;elseparent->right = & z;//插入到右分支z.left = T.nil;z.right = T.nil;z.color = RED;//被插入点,默认都是红色RB_insert_fixup(T, &z);//由于可能存在两个连续的红色节点,从而破外红黑树的性质,故需要修正return true;}RBTree::Node * RBTree::RB_delete(RBTree & T, Node & z){//z 是要删除的节点//y 是真正被删除的节点//x 是真正被删除节点的右孩子Node * y = &z;Node * x;bool y_orig_color = y->color;if(z.left == T.nil)//被删除节点左为空{x = z.right;RB_transplant(T, z, *(z.right));}else if(z.right == T.nil)//被删除节点右为空{x = z.left;RB_transplant(T, z, *(z.left));}else//被删除节点左右不为空: y迁移到z的位置,并保留z的颜色,x填充y的位置{y = tree_minimum(T,*(z.right));//y节点的左侧最小的节点即是真正被删除的节点y_orig_color = y->color;x = y->right;///* case 1z \  y   \    x*/if(y->p == &z)       //case1{x->p = y;}else{//case2/* case 2z \ ... \  y   \x*/RB_transplant(T, *y, *(y->right));//将x填充到y的位置y->right = z.right;//复制z的右分支到yy->right->p = y;}RB_transplant(T, z, *y);//将y迁移到z的位置,保留z的颜色和左右分支信息//复制z的左分支到yy->left = z.left;y->left->p = y;//将y染成z的颜色y->color = z.color;}if(y_orig_color == BLACK)//当y为黑色时,删除y会导致y附近的黑高速减少1,所以需要重新重新旋转和着色RB_delete_fixup(T, x);return  &z;}RBTree::Node * RBTree::RB_search(RBTree & T, int key){return NULL;}RBTree::Node *  RBTree::RB_next(RBTree & T){return NULL;}RBTree::Node *  RBTree::RB_next(const Node * z){return NULL;}#include <stdlib.h>#include <time.h>int main(){bool exit = true;string temp;RBTree Tree;cout<<"Tree.nil "<<Tree.nil<<endl;cout <<"please input key of node to be inserted !!!"<<endl;srand(time(NULL));//while(exit)for(int i=0;i<6;i++){RBTree::Node * new_node = new RBTree::Node(); cin>> new_node->key;cout<<"key:"<<new_node->key<<endl;//new_node->key = rand()%191 +10; // 10 <= key <= 200Tree.RB_insert(Tree, *new_node);}cout << "red black tree contructed!!!\n";cout <<"begin to delete the root the first time\n";RBTree::Node * tmp;tmp = Tree.RB_delete(Tree, *(Tree.Root));cout <<"begin to delete the root the first time finihed: "<<tmp->key<<endl;delete tmp;tmp = Tree.RB_delete(Tree, *(Tree.Root));cout <<"begin to delete the root the second time finihed: "<<tmp->key<<endl;delete tmp;tmp = Tree.RB_delete(Tree, *(Tree.Root));cout <<"begin to delete the root the third time finihed: "<<tmp->key<<endl;delete tmp;tmp = Tree.RB_delete(Tree, *(Tree.Root));cout <<"begin to delete the root the fourth time finihed: "<<tmp->key<<endl;delete tmp;cout <<"test end!"<<endl;cout<<"godby"<<endl;system("pause");return 0;}

5 参考资料

1  <<算法导论>>
2  <<c++ primer>>
3  红黑树的删除实例

0 0