数据结构大总结系列之红黑树
2012-08-14 20:03 javaspring 阅读(393) 评论(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);
}






浙公网安备 33010602011771号