数据结构大总结系列之红黑树
2012-08-14 20:03 javaspring 阅读(379) 评论(0) 编辑 收藏 举报一,红黑树的性质:
红黑树本质是二叉查找树的一种,它的性能高于普通的二叉查找树,即使是在最坏的情况下也能保证时间复杂度为O(lgn)。红黑树在每个结点上增加一个存储位表示结点的颜色(或红或黑)。通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树可以保证没有一条路径会比其他路径长出两倍,因而是接近平衡的。
红黑树的每个结点至少包含五个域:color,key,left,right 和 parent,如果某结点没有子结点或者父结点,则该结点相应的指针(p)域包含值NIL,我们将这些 NIL 当作叶子结点.(图a)。
在实际处理过程中,往往将最底层的孩子结点和根结点的父亲都指向同一个 NIL 结点,以便于处理红黑树代码中的边界条件(这里的哨兵NIL是一个与树内普通结点有相同域的对象,它的color域为BLACK,而它的其他域——parent, left, right, key,可以设置为任意允许的值),而将其它结点当作内结点。 (图b)
通常表示红黑树的方法是图(c),即忽略叶子与根部的父结点。(图c),图中,黑结点用黑色表示,红结点用浅阴影表示。
红黑树在很多地方都有应用。在C++ STL中,很多部分(目前包括set, multiset, map, multimap)应用了红黑树的变体(SGI STL中的红黑树有一些变化,这些修改提供了更好的性能,以及对set操作的支持)。
一棵树满足下边的红黑树性质,则它就是一棵红黑树:
性质1. 结点是红色或黑色。
性质2. 根是黑色。
性质3 每个叶结点是黑色的。
性质4 每个红色结点的两个子结点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色结点)
性质5. 从任一结点到其每个叶子的所有路径都包含相同数目的黑色结点。
红黑树数据结构:
const int RED = 0;
const int BLACK = 1;
typedef struct RBTNode RBT;
typedef struct RBTNode * Position;
typedef struct RBTNode * SearchTree;
//红黑树数据结构
struct RBTNode{
int key;
int color;
Position parent, left, right;
};
static RBT sentinel = {0, 1, 0, 0, 0}; //哨兵
#define NIL &sentinel
红黑树的查找,最小值,最大值,前驱,后继操作和二叉查找树基本一样,请参见前面的博文数据结构大总结系列之基本查找算法
二,旋转:
旋转(Rotate):红黑树的插入和删除操作可能会破坏上边列出的红黑树性质,所以需要通过旋转,来保持红黑树性质,看看这幅图,仔细观察下,左图到右图是LeftRotate,右图到左图是RightRotate,旋转的意思是:整个图(其实就是x,y之间的链)以从左到右水平方向为轴,旋转180°,x,y位置就对调了,然后跟换结果子结点的指针就OK了。
左旋伪代码:
旋转代码实现:
//左旋,旋转只有指针被改变,其他域保持不变 void LeftRotate(SearchTree &tree, Position x) { Position y = x->right; //set y's z左子树 into x's 右子树 x->right = y->left; if (y->left != NIL) { y->left->parent = x; } //下面代码修改x的父节点与y的关系 y->parent = x->parent; if (x->parent == NIL) {//如果x是根节点 tree = y; } else if (x == x->parent->left) {//如果x不是根节点 x->parent->left = y; } else { x->parent->right = y; } //修改x与y的关系 y->left = x; x->parent = y; } //右旋,只要把左旋里的程序对称就行了 void RightRotate(SearchTree &tree, Position x) { Position y = x->left; //set y's 右子树 into x's 左子树 x->left = y->right; if (y->right != NIL) { y->right->parent = x; } //下面代码修改x的父节点与y的关系 y->parent = x->parent; if (x->parent == NIL) {//如果x是根节点 tree = y; } else if (x == x->parent->left) {//如果x不是根节点 x->parent->left = y; } else { x->parent->right = y; } //修改x与y的关系 y->right = x; x->parent = y; }
插入(RBTreeInsert)::红黑树的插入过程大致是和二叉查找树的插入过程差不多的,只是略微修改了下,因为插入新结点可能会破坏红黑树性质,所以要调用一个辅助函数RbInsertFixup()来对结点重新着色并旋转。因此红黑树插入操作的重点是辅助函数RbInsertFixup()
插入操作伪代码:
RBTreeInsert和BSTreeInsert区别有四点:
1)RBTreeInsert内所有NULL(代表空)都用NIL(代表一个结点)代替;
2)14,15行设置z.left和z.right为NIL,来保持正确的树结构。
3)16行将z着色为红色
4)z设置为红色可能会违反某条红黑性质,所以在17行调用RBInsertFixup(T, x)来保持红黑性质
插入有三种情况,所以相应的RBInsertFixup(T, x)case1,2,3分别处理三种情况。
如图:
Case1 : z的叔叔y是红色的,如图(a), (b)
A、B都为红色,破坏了性质4,因为要保证黑高度不变(性质5),同时满足性质4,只能让A、D变为黑色,而B、C变为红色(567行),这样就满足所有红黑性质了,然后将C设定为新的z,然后向上迭代。
Case2:z的叔叔y是黑色,而且z是右孩子
Case3:z的叔叔y是黑色,而且z是左孩子
从图(c)中可以看出,Case2和Case3仅仅是孩子位置不同,所以可以将Case2进行一个左旋转变为Case3,此时破坏了性质4,所以需要重新着色来保证性质4,将B着色黑色,C着色红色,此时性质4满足,但是性质5破坏了,所以再对C进行一次右旋,得到最终结果,此时满足所有性质。
PS:
1)调用RBInsertFixup(T, x)时,只可能破坏红黑性质的第2,4(2->根结点为黑色,4->一个红结点不能有红子女),其他的没影响。所以只需判断性质2,4即可。
2)循环每次迭代都会有两种可能:指针z沿着树上移,或者执行某些旋转然后循环结束。
实例:
插入代码实现:
//插入的FixUp void RBTreeInsertFixUp(SearchTree &tree, Position z) { while (z->parent->color == RED) { if (z->parent = z->parent->parent->left) {//y在右边 Position y = z->parent->parent->right;//设定y if (y->color == RED) {//Case1 z->parent->color = BLACK; y->color = BLACK; z->parent->parent ->color = RED; z = z->parent->parent; } else { if (z == z->parent->right) {//Case2 z = z->parent;//旋转会改变新结点的位置,所以要调整 LeftRotate(tree, z); //case2->case3 } z->parent->color = BLACK;//Case3 z->parent->parent->color = RED; RightRotate(tree, z->parent->parent); } } else {//y在左边 Position y = z->parent->parent->left; if (y->color == RED) { z->parent->color = BLACK; y->color = BLACK; z->parent->parent ->color = RED; z = z->parent->parent; } else { if (z == z->parent->left) {//Case2 z = z->parent;//旋转会改变新结点的位置,所以要调整 RightRotate(tree, z); //case2->case3 } z->parent->color = BLACK;//Case3 z->parent->parent->color = RED; LeftRotate(tree, z->parent->parent); } }//if-else }//while tree->color = BLACK; }//RBInsertFixUp //插入操作 void RBTreeInsert(SearchTree &tree, int k) { Position y = NIL; Position x = tree; Position z = new RBT; z->key = k; z->parent = z->left = z->right = NIL;//初始化 //找到要插入的位置 while (x != NIL) { y = x; if (z->key < x->key) { x = x->left; } else { x = x->right; } } //和周边点增加关系 z->parent = y; if (y == NIL) { tree = z; } else { if (k < y->key) { y->left = z; } else { y->right = z; } } z->left = z->right = NIL; z->color = RED; RBTreeInsertFixUp(tree, z); }