今年的第一篇博客,距离上一篇博客竟然已经一个月了。最近都静不下心来学习 ,一则刚过完年,二则刚换工作。好了,不扯了,转入正题吧。
今天讨论的是红黑树,红黑树有一个特点:保证了树结构是基本平衡的(即没有一条路径会比其他路径长出两倍)。除了这个特点之外,红黑树和一般的二叉搜索树差距不大。
问题是怎样在不断的进行插入、删除等动态操作时,保持树的基本平衡呢?从直觉上可以推测出只有红黑树的根节点在插入、删除节点的过程中不断改变才可能实现树的高度平衡,事实上也的确如此。这一点是和普通二叉树不同的地方,即在插入、删除节点时,红黑树中的各节点的颜色属性可能发生改变,同时红黑树的根节点也不是固定不变的。下面是红黑树的红黑性质,再是红黑树的插入、删除操作的详细讨论。
红黑性质:
1.每个节点不是黑色就是红色的
2.根节点是黑色的
3.每个叶节点(NIL)是黑色的
4.如果一个节点是红色的,那么它的两个子女都是黑色的
5.从某一个节点到达其子孙叶节点的每一条简单路径上包含相同数量的黑色节点
下面是一个红黑树的Java实现,主要实现的是插入和删除操作
插入过程:
首先插入的节点是红色的,为什么不能是黑色的?因为红色的节点可能会破坏性质4,但是维护了性质5。性质4的纠正可以通过上移调整节点的位置来实现;而如果性质5被破坏后,纠正起来就相对复杂了。
添加完节点后,有以下几种情况:
一、父节点和叔节点是红色的,可以根据性质4推断祖父节点必为黑色的,称为情况一。这种情况的处理方法最简单,只需要将父节点、叔节点都变成黑色,再将祖父节点变成红色,这样做维护了性质5不被破坏。然后因为祖父节点变成了红色,可能会破坏性质4,此时再将调整节点的指针指向祖父节点继续调整即可。
二、父节点红色,叔节点黑色,且祖父、父节点、本节点三者成"Z"字形,称为情况二。
三、父节点红色,叔节点黑色,且祖父、父节点、本节点三者在一条斜线上,称为情况三。
删除过程:
删除过程又分为很多种情况,但是不管哪种情况都先要进行普通搜索二叉树的删除操作,只不过需要在删除操作之前进行缓存节点的初始颜色,在完成删除操作后,会根据该初始颜色决定是否需要进行调整修复操作。
删除完节点后,有以下几种情况(注意以下讨论假设了删除节点位于子树的左侧):
一、删除节点(注意这里的删除节点指的是真正删除掉的节点)的兄弟节点是红色的,此时可以断定父节点是黑色的,则可以通过交换父节点和兄弟节点的颜色,并对父节点进行一次左旋操作即可。
二、兄弟节点是黑色的,且兄弟节点的左右子节点都是黑色的,此时可将兄弟节点变成红色的,来使的以父节点为根节点的子树保持平衡,然后再将父节点指定为当前调整节点网上调整即可。
三、兄弟节点是黑色的,且兄弟节点的左孩子是红色的,右孩子是黑色的。此时需要兄弟节点和兄弟节点左节点的颜色并对兄弟节点进行右旋转操作,这样可以将情况三转换成情况四。
四、兄弟节点是黑色的,兄弟节点的右孩子是红色的。改变兄弟节点、父节点、兄弟节点右孩子的颜色,并对父节点进行左旋操作。
实现代码:
节点类RBNode
package com.wly.rbtree_wly;
/**
* 红黑树节点类
* @author wly
*
*/
public class RBNode {
private final int RED = 0;
private final int BLACK = 1;
RBNode left,right,p;
public int key; //检索关键字
public int color;
// public static RBNode nil = new RBNode();
public boolean isRed() {
return (color == RED);
}
public boolean isBlack() {
return (color == BLACK);
}
public RBNode() {
// left = new RBNode();
// right = new RBNode();
// p = new RBNode();
color = BLACK;
}
public RBNode(int key) {
this();
this.key = key;
}
}
红黑树类RBTree
package com.wly.rbtree_wly;
/**
* 红黑树实现类
* @author wly
*
*/
public class RBTree {
private final int RED = 0;
private final int BLACK = 1;
public final RBNode nil;
static RBNode root = null;
public RBTree() {
//初始化哨兵对象,简化边界情况的处理
nil = new RBNode();
nil.left = new RBNode();
nil.right = new RBNode();
root = nil;
}
public static void main(String[] args) {
RBNode node1 = new RBNode(50);
RBNode node2 = new RBNode(25);
RBNode node3 = new RBNode(75);
RBNode node4 = new RBNode(10);
RBNode node5 = new RBNode(80);
RBNode node6 = new RBNode(100);
RBNode node7 = new RBNode(60);
RBTree tree = new RBTree();
tree.insert(node1);
tree.insert(node2);
tree.insert(node3);
tree.insert(node4);
tree.insert(node5);
tree.insert(node6);
tree.insert(node7);
System.out.println();
tree.delete(root.left);
System.out.println();
tree.delete(root.right);
System.out.println();
}
/**
* 插入节点
*/
public void insert(RBNode n) {
if(root == nil) {
n.color = BLACK;
n.left = nil;
n.right = nil;
n.p = nil;
root = n;
return ;
}
RBNode temp = root;
RBNode t = nil;
while(temp != nil) {
t = temp;
if(n.key < temp.key) {
temp = temp.left;
} else {
temp = temp.right;
}
}
if(t.key > n.key) {
t.left = n;
n.p = t;
} else {
t.right = n;
n.p = t;
}
n.color = RED; //将颜色涂成红色
n.left = nil;
n.right = nil;
insertFixUP(n);
}
/**
* 插入节点后的修复
*/
public void insertFixUP(RBNode z) {
RBNode y = null;
while(z.p.isRed()) {
if(z.p == z.p.p.left) {
y = z.p.p.right; //叔节点
//情况一,父节点和叔节点都是红色的,将父节点、叔节点都涂成黑色,将祖父节点涂成红色,并将调整节点指针上移至祖父节点
if(y.isRed()) {
z.p.color = BLACK;
y.color = BLACK;
z.p.p.color = RED;
z = z.p.p;// 移动调整节点位置到祖父节点
//情况三,本节点、父节点、祖父节点成z字形;此时需先通过一次左旋转来讲情况转换成情况二
} else if(z == z.p.right) {
z = z.p; //移动调整节点位置到父节点
rotateLeft(z);
}
//情况二,本节点、父节点、祖父节点成一条直线,可以直接通过一次右选择及重新上色来加以调整
else {
z.p.color = BLACK;
z.p.p.color = RED;
rotateRight(z.p.p);
}
} else {
y = z.p.p.left; //叔节点
if(y.isRed()) {
z.p.color = BLACK;
y.color = BLACK;
z.p.p.color = RED;
z = z.p.p;// 移动调整节点位置到祖父节点
} else if(z == z.p.left) {
z = z.p; //移动调整节点位置到父节点
rotateRight(z);
}
else {
z.p.color = BLACK;
z.p.p.color = RED;
rotateLeft(z.p.p);
}
}
}
root.color = BLACK;
}
/**
* 右旋转
*/
public void rotateRight(RBNode x) {
RBNode y = x.left;
x.left = y.right;
if(y.right != nil) {
y.right.p = x;
}
y.p = x.p;
if(x.p == nil) {
root = y;
} else if (x == x.p.right){
x.p.right = y;
} else {
x.p.left = y;
}
y.right = x;
x.p = y;
}
/**
* 左旋转
*/
public void rotateLeft(RBNode x) {
RBNode y = x.right;
x.right = y.left;
if(y.left != nil) {
y.left.p = x;
}
y.p = x.p;
if(x.p == nil) {
root = y;
} else if (x == x.p.left){
x.p.left = y;
} else {
x.p.right = y;
}
y.left = x;
x.p = y;
}
/**
* 使用v替代u,在删除过程中使用
* @param u
* @param v
*/
public void transplant(RBNode u,RBNode v) {
if(u.p == nil) {
root = v;
} else if(u == u.p.left) {
u.p.left = v;
} else {
u.p.right = v;
}
v.p = u.p;
}
/**
* 得到节点n的后继节点
* @param n
* @return
*/
public RBNode successor(RBNode n) {
RBNode temp = null;
while(n != nil) {
temp = n;
if(temp.key > n.key) {
n = n.right;
} else {
n = n.left;
}
}
return temp;
}
/**
* 删除节点
*/
public void delete(RBNode z) {
//1.缓存待删除节点的原有颜色
RBNode y = z;
RBNode x;
int y_original_color = y.color;
if(z.left == nil) { //2.待删除节点的左节点为空
x = z.right;
transplant(z, z.right);
} else if(z.right == nil) { //3.待删除节点的右节点为空
x = z.left;
transplant(z, z.left);
} else {//4.左右节点都不为空,寻找待删除节点的后继节点
y = successor(z);
y_original_color = y.color;//因为y的位置发生了改变,所以重新缓存y的初始颜色
x = y.right; //y不可能有左子节点,可能有右子节点
if(y.p == z) { //y是z的直接子节点
x.p = y;
} else {
transplant(y, y.right); //将y从树结构中移除
y.right = z.right;
y.right.p = y;
}
z.key = y.key; //将后继节点的key赋值给删除节点
y.left = z.left;
y.left.p = y;
y.color = z.color; //将z的颜色赋值给y,注意此时y的初始颜色已经被缓存起来了。
}
//5.判断"最终删除节点"的颜色是否为黑色,如果是黑色的话,会导致性质4被破坏,需要调整
if(y_original_color == BLACK) {
deleteFixUP(x);
}
}
/**
* 删除节点后的修复
*/
public void deleteFixUP(RBNode x) {
while(!x.equals(root) && x.isBlack()) { //x为红色时,不影响树的黑高平衡
if(x == x.p.left) {
RBNode w = x.p.right;
//1.兄弟节点w是红色的,可以推断父节点为黑色的,只需将父节点设置
if(w.isRed()) {
w.color = BLACK;
x.p.color = RED;
rotateLeft(x.p);
w = x.p.right;
}
//2.兄弟节点w是黑色的,兄弟节点的两个子节点都是黑色的
//思路:这里的删除会引起左侧子数的黑高减一,那么为了将调整节点上移,需要先将右侧子数的黑高也减一,然后将调整节点上移
//虽然这里删除的节点不是x.p,但是作为树结构的调整是可以的。如果x.p是红色的,则退出while循环,因为在函数的最后会单独将x.p设置
//为黑色的,以弥补x.p的左右子数的黑高缺失,如果x.p是黑色的,则继续调整
if(w.left.isBlack() && w.right.isBlack()) {
w.color = RED; //将右侧子数的黑高减一
x = x.p; //同时将调节节点位置指向待删除节点的父节点
} else {
if(w.left.isRed() && w.right.isBlack()) { //3.兄弟节点的左子节点是红色的,转换成情况四,然后再行求解
w.left.color = BLACK;
w.color = RED;
rotateRight(w);
w = x.p.right;
}
if(w.right.isRed()) { //4.兄弟节点的右子节点是红色的
w.color = x.p.color;
x.p.color = BLACK;
w.right.color = BLACK;
rotateLeft(x.p);
x = root; //通过将x指向root来退出while循环
}
}
} else {
RBNode w = x.p.left;
//1.兄弟节点w是红色的,可以推断父节点为黑色的,只需将父节点设置
if(w.isRed()) {
w.color = BLACK;
x.p.color = RED;
rotateRight(x.p);
w = x.p.left;
}
//2.兄弟节点w是黑色的,兄弟节点的两个子节点都是黑色的
if(w.right.isBlack() && w.left.isBlack()) {
w.color = RED;
x = x.p;
} else {
if(w.right.isRed() && w.left.isBlack()) { //3.兄弟节点的左子节点是红色的
w.right.color = BLACK;
w.color = RED;
rotateLeft(w);
w = x.p.left;
}
if(w.left.isRed()) { //4.兄弟节点的右子节点是红色的
//4.兄弟节点的右子节点是红色的
w.color = x.p.color;
x.p.color = BLACK;
w.left.color = BLACK;
rotateRight(x.p);
x = root; //通过将x指向root来退出while循环
}
}
}
}
x.color = BLACK;
}
}
好了,情况多的让人有点头疼的红黑树就到此为止了,因为上图说明的话实在有些耗费时间,笔者这里及偷懒一下了,权当代码备份好了。 :)
参考文档:http://download.csdn.net/detail/u011638883/6960637(红黑树的插入与删除_详细整理资料)
转载请保留出处:http://blog.csdn.net/u011638883/article/details/18953963
谢谢!!