基本概念
R-B Tree,全称是Red-Black Tree,又称为“红黑树”,它一种特殊的二叉查找树。红黑树的每个节点上都有存储位表示节点的颜色,可以是红(Red)或黑(Black)。 R-B Tree,全称是Red-Black Tree,又称为“红黑树”,它一种特殊的二叉查找树。红黑树的每个节点上都有存储位表示节点的颜色,可以是红(Red)或黑(Black)。
红黑树的特性:
(1)每个节点或者是黑色,或者是红色。
(2)根节点是黑色。
(3)每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!]
(4)如果一个节点是红色的,则它的子节点必须是黑色的。
(5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。
注意:
(01) 特性(3)中的叶子节点,是只为空(NIL或null)的节点。
(02) 特性(5),确保没有一条路径会比其他路径长出俩倍。因而,红黑树是相对是接近平衡的二叉树。
R-B Tree时间复杂度
红黑树的时间复杂度为: O(lgn)
下面通过“数学归纳法”对红黑树的时间复杂度进行证明。
定理:一棵含有n个节点的红黑树的高度至多为2log(n+1).
证明: "一棵含有n个节点的红黑树的高度至多为2log(n+1)" 的逆否命题是 "高度为h的红黑树,它的包含的内节点个数至少为 2^{h/2}-1个"。 我们只需要证明逆否命题,即可证明原命题为真;即只需证明 "高度为h的红黑树,它的包含的内节点个数至少为 2^{h/2}-1个"。 从某个节点x出发(不包括该节点)到达一个叶节点的任意一条路径上,黑色节点的个数称为该节点的黑高度,记为bh(x)。 由红黑树的"特性(4)"可知 bh(x)>=h/2;进而,我们只需证明 "高度为h的红黑树,它的包含的内节点个数至少为 2^bh(x)-1个"即可。 到这里,我们将需要证明的定理已经由"一棵含有n个节点的红黑树的高度至多为2log(n+1)" 转变成只需要证明"高度为h的红黑树,它的包含的内节点个数至少为 2^bh(x)-1个"。
下面通过"数学归纳法"开始论证高度为h的红黑树,它的包含的内节点个数至少为 2^bh(x)-1个"。
(01) 当树的高度h=0时,内节点个数是0,bh(x) 为0,2^bh(x)-1 也为 0。显然,原命题成立。
(02) 当h>0,且树的高度为 h-1 时,它包含的节点个数至少为 2^{bh(x)-1}-1。这个是根据(01)推断出来的! 下面,由树的高度为 h-1 的已知条件推出“树的高度为 h 时,它所包含的节点树为 2^bh(x)-1”。 当树的高度为 h 时, 对于节点x(x为根节点),其黑高度为bh(x)。 对于节点x的左右子树,它们黑高度为 bh(x) 或者 bh(x)-1。 根据(02)的已知条件,我们已知 "x的左右子树,即高度为 h-1 的节点,它包含的节点至少为 2^{bh(x)-1}-1 个"; 所以,节点x所包含的节点至少为 ( 2^{bh(x)-1}-1 ) + ( 2^{bh(x)-1}-1 ) + 1 = 2^{bh(x)-1}。即节点x所包含的节点至少为 2^{bh(x)-1} 。 因此,原命题成立。 由(01)、(02)得出,"高度为h的红黑树,它的包含的内节点个数至少为 2^bh(x)-1个"。因此,“一棵含有n个节点的红黑树的高度至多为2log(n+1)”。
2.平衡性的修正
红-黑树主要通过三种方式对平衡进行修正,改变节点颜色、左旋和右旋。这看起来有点抽象,我们分别来介绍它们。
1.变色
改变节点颜色比较容易理解,因为它违背了规则3。假设现在有个节点E,然后插入节点A和节点S,节点A在左子节点,S在右子节点,目前是平衡的。如果此时再插一个节点,那么就出现了不平衡了,因为红色节点的子节点必须为黑色,但是新插的节点是红色的。所以这时候就必须改变节点颜色了。所以我们将根的两个子节点从红色变为黑色(至于为什么都要变,下面插入的时候会详细介绍),将父节点会从黑色变成红色。可以用如下示意图表示一下:
2.左旋
通常左旋操作用于将一个向右倾斜的红色链接旋转为向左链接。示意图如下:
左旋有个很萌萌哒的动态示意图,可以方便理解:
3.右旋
右旋可左旋刚好相反,这里不再赘述,直接看示意图:
当然咯,右旋也有个萌萌的动态图:
这里主要介绍了红-黑树对平衡的三种修正方式,大家有个感性的认识,那么什么时候该修正呢?什么时候该用哪种修正呢?这将是接下来我们要探讨的问题。
红黑树的操作
红-黑树的基本操作是添加、删除和旋转。对红-黑树进行添加或删除后,可能会破坏其平衡性,会用到哪种旋转方式去修正呢?我们首先对红-黑树的节点做一介绍,然后分别对左旋和右旋的具体实现做一分析,最后我们探讨下红-黑树的具体操作。
3.0.红-黑树的节点
红-黑树是对二叉搜索树的改进,所以其节点与二叉搜索树是差不多的,只不过在它基础上增加了一个boolean型变量来表示节点的颜色,具体看RBNode<T>类:
public class RBNode<T extends Comparable<T>>{
boolean color; //颜色
T key; //关键字(键值)
RBNode<T> left; //左子节点
RBNode<T> right; //右子节点
RBNode<T> parent; //父节点
public RBNode(T key, boolean color, RBNode<T> parent, RBNode<T> left, RBNode<T> right) {
this.key = key;
this.color = color;
this.parent = parent;
this.left = left;
this.right = right;
}
public T getKey() {
return key;
}
public String toString() {
return "" + key + (this.color == RED? "R" : "B");
}
}
3.1 左旋
//算法导论伪代码
LEFT-ROTATE(T, x)
01 y ← right[x] // 前提:这里假设x的右孩子为y。下面开始正式操作
02 right[x] ← left[y] // 将 “y的左孩子” 设为 “x的右孩子”,即 将β设为x的右孩子
03 p[left[y]] ← x // 将 “x” 设为 “y的左孩子的父亲”,即 将β的父亲设为x
04 p[y] ← p[x] // 将 “x的父亲” 设为 “y的父亲”
05 if p[x] = nil[T]
06 then root[T] ← y // 情况1:如果 “x的父亲” 是空节点,则将y设为根节点
07 else if x = left[p[x]]
08 then left[p[x]] ← y // 情况2:如果 x是它父节点的左孩子,则将y设为“x的父节点的左孩子”
09 else right[p[x]] ← y // 情况3:(x是它父节点的右孩子) 将y设为“x的父节点的右孩子”
10 left[y] ← x // 将 “x” 设为 “y的左孩子”
11 p[x] ← y // 将 “x的父节点” 设为 “y”
/*************对红黑树节点x进行左旋操作 ******************/
/*
* 左旋示意图:对节点x进行左旋
* p p
* / /
* x y
* / \ / \
* lx y -----> x ry
* / \ / \
* ly ry lx ly
* 左旋做了三件事:
* 1. 将y的左子节点赋给x的右子节点,并将x赋给y左子节点的父节点(y左子节点非空时)
* 2. 将x的父节点p(非空时)赋给y的父节点,同时更新p的子节点为y(左或右)
* 3. 将y的左子节点设为x,将x的父节点设为y
*/
private void leftRotate(RBNode<T> x) {
//1. 将y的左子节点赋给x的右子节点,并将x赋给y左子节点的父节点(y左子节点非空时)
RBNode<T> y = x.right;
x.right = y.left;
if(y.left != null)
y.left.parent = x;
//2. 将x的父节点p(非空时)赋给y的父节点,同时更新p的子节点为y(左或右)
y.parent = x.parent;
if(x.parent == null) {
this.root = y; //如果x的父节点为空,则将y设为父节点
} else {
if(x == x.parent.left) //如果x是左子节点
x.parent.left = y; //则也将y设为左子节点
else
x.parent.right = y;//否则将y设为右子节点
}
//3. 将y的左子节点设为x,将x的父节点设为y
y.left = x;
x.parent = y;
}
3.2 右旋
RIGHT-ROTATE(T, y)
01 x ← left[y] // 前提:这里假设y的左孩子为x。下面开始正式操作
02 left[y] ← right[x] // 将 “x的右孩子” 设为 “y的左孩子”,即 将β设为y的左孩子
03 p[right[x]] ← y // 将 “y” 设为 “x的右孩子的父亲”,即 将β的父亲设为y
04 p[x] ← p[y] // 将 “y的父亲” 设为 “x的父亲”
05 if p[y] = nil[T]
06 then root[T] ← x // 情况1:如果 “y的父亲” 是空节点,则将x设为根节点
07 else if y = right[p[y]]
08 then right[p[y]] ← x // 情况2:如果 y是它父节点的右孩子,则将x设为“y的父节点的左孩子”
09 else left[p[y]] ← x // 情况3:(y是它父节点的左孩子) 将x设为“y的父节点的左孩子”
10 right[x] ← y // 将 “y” 设为 “x的右孩子”
11 p[y] ← x // 将 “y的父节点” 设为 “x”
/*************对红黑树节点y进行右旋操作 ******************/
/*
* 左旋示意图:对节点y进行右旋
* p p
* / /
* y x
* / \ / \
* x ry -----> lx y
* / \ / \
* lx rx rx ry
* 右旋做了三件事:
* 1. 将x的右子节点赋给y的左子节点,并将y赋给x右子节点的父节点(x右子节点非空时)
* 2. 将y的父节点p(非空时)赋给x的父节点,同时更新p的子节点为x(左或右)
* 3. 将x的右子节点设为y,将y的父节点设为x
*/
private void rightRotate(RBNode<T> y) {
//1. 将y的左子节点赋给x的右子节点,并将x赋给y左子节点的父节点(y左子节点非空时)
RBNode<T> x = y.left;
y.left = x.right;
if(x.right != null)
x.right.parent = y;
//2. 将x的父节点p(非空时)赋给y的父节点,同时更新p的子节点为y(左或右)
x.parent = y.parent;
if(y.parent == null) {
this.root = x; //如果x的父节点为空,则将y设为父节点
} else {
if(y == y.parent.right) //如果x是左子节点
y.parent.right = x; //则也将y设为左子节点
else
y.parent.left = x;//否则将y设为右子节点
}
//3. 将y的左子节点设为x,将x的父节点设为y
x.right = y;
y.parent = x;
}
3.3 添加操作
第一步: 将红黑树当作一颗二叉查找树,将节点插入。 红黑树本身就是一颗二叉查找树,将节点插入后,该树仍然是一颗二叉查找树。也就意味着,树的键值仍然是有序的。此外,无论是左旋还是右旋,若旋转之前这棵树是二叉查找树,旋转之后它一定还是二叉查找树。这也就意味着,任何的旋转和重新着色操作,都不会改变它仍然是一颗二叉查找树的事实。 第二步:将插入的节点着色为"红色"。 将插入的节点着色为红色,不会违背"特性(5)"!少违背一条特性,就意味着我们需要处理的情况越少。接下来,就要努力的让这棵树满足其它性质即可;满足了的话,它就又是一颗红黑树了。o(∩∩)o...哈哈
第三步: 通过一系列的旋转或着色等操作,使之重新成为一颗红黑树。
根据被插入节点的父节点的情况,可以将"当节点z被着色为红色节点,并插入二叉树"划分为三种情况来处理。 根据被插入节点的父节点的情况,可以将"当节点z被着色为红色节点,并插入二叉树"划分为三种情况来处理。
① 情况说明:被插入的节点是根节点。 处理方法:直接把此节点涂为黑色。
② 情况说明:被插入的节点的父节点是黑色。 处理方法:什么也不需要做。节点被插入后,仍然是红黑树。
③ 情况说明:被插入的节点的父节点是红色。 处理方法:那么,该情况与红黑树的“特性(5)”相冲突。这种情况下,被插入节点是一定存在非空祖父节点的;进一步的讲,被插入节点也一定存在叔叔节点(即使叔叔节点为空,我们也视之为存在,空节点本身就是黑色节点)。理解这点之后,我们依据"叔叔节点的情况",将这种情况进一步划分为3种情况(Case)。
分析完了红-黑树中主要的旋转操作,接下来我们开始分析常见的插入、删除等操作了。这里先分析插入操作。 由于红-黑树是二叉搜索树的改进,所以插入操作的前半工作时相同的,即先找到待插入的位置,再将节点插入,先来看看插入的前半段代码:
RB-INSERT(T, z)
y ← nil[T] // 新建节点“y”,将y设为空节点。
x ← root[T] // 设“红黑树T”的根节点为“x”
while x ≠ nil[T] // 找出要插入的节点“z”在二叉树T中的位置“y”
do y ← x
if key[z] < key[x]
then x ← left[x]
else x ← right[x]
p[z] ← y // 设置 “z的父亲” 为 “y”
if y = nil[T]
then root[T] ← z // 情况1:若y是空节点,则将z设为根
else if key[z] < key[y]
then left[y] ← z // 情况2:若“z所包含的值” < “y所包含的值”,则将z设为“y的左孩子”
else right[y] ← z // 情况3:(“z所包含的值” >= “y所包含的值”)将z设为“y的右孩子”
left[z] ← nil[T] // z的左孩子设为空
right[z] ← nil[T] // z的右孩子设为空。至此,已经完成将“节点z插入到二叉树”中了。
color[z] ← RED // 将z着色为“红色”
RB-INSERT-FIXUP(T, z) // 通过RB-INSERT-FIXUP对红黑树的节点进行颜色修改以及旋转,让树T仍然是一颗红黑树
结合伪代码以及为代码上面的说明,先理解RB-INSERT。理解了RB-INSERT之后,我们接着对 RB-INSERT-FIXUP的伪代码进行说明。
添加修正操作的伪代码《算法导论》
RB-INSERT-FIXUP(T, z)
while color[p[z]] = RED // 若“当前节点(z)的父节点是红色”,则进行以下处理。
do if p[z] = left[p[p[z]]] // 若“z的父节点”是“z的祖父节点的左孩子”,则进行以下处理。
then y ← right[p[p[z]]] // 将y设置为“z的叔叔节点(z的祖父节点的右孩子)”
if color[y] = RED // Case 1条件:叔叔是红色
then color[p[z]] ← BLACK ▹ Case 1 // (01) 将“父节点”设为黑色。
color[y] ← BLACK ▹ Case 1 // (02) 将“叔叔节点”设为黑色。
color[p[p[z]]] ← RED ▹ Case 1 // (03) 将“祖父节点”设为“红色”。
z ← p[p[z]] ▹ Case 1 // (04) 将“祖父节点”设为“当前节点”(红色节点)
else if z = right[p[z]] // Case 2条件:叔叔是黑色,且当前节点是右孩子
then z ← p[z] ▹ Case 2 // (01) 将“父节点”作为“新的当前节点”。
LEFT-ROTATE(T, z) ▹ Case 2 // (02) 以“新的当前节点”为支点进行左旋。
color[p[z]] ← BLACK ▹ Case 3 // Case 3条件:叔叔是黑色,且当前节点是左孩子。(01) 将“父节点”设为“黑色”。
color[p[p[z]]] ← RED ▹ Case 3 // (02) 将“祖父节点”设为“红色”。
RIGHT-ROTATE(T, p[p[z]]) ▹ Case 3 // (03) 以“祖父节点”为支点进行右旋。
else (same as then clause with "right" and "left" exchanged) // 若“z的父节点”是“z的祖父节点的右孩子”,将上面的操作中“right”和“left”交换位置,然后依次执行。
color[root[T]] ← BLACK
/*********************** 向红黑树中插入节点 **********************/
public void insert(T key) {
RBNode<T> node = new RBNode<T>(key, RED, null, null, null);
if(node != null)
insert(node);
}
//将节点插入到红黑树中,这个过程与二叉搜索树是一样的
private void insert(RBNode<T> node) {
RBNode<T> current = null; //表示最后node的父节点
RBNode<T> x = this.root; //用来向下搜索用的
//1. 找到插入的位置
while(x != null) {
current = x;
int cmp = node.key.compareTo(x.key);
if(cmp < 0)
x = x.left;
else
x = x.right;
}
node.parent = current; //找到了位置,将当前current作为node的父节点
//2. 接下来判断node是插在左子节点还是右子节点
if(current != null) {
int cmp = node.key.compareTo(current.key);
if(cmp < 0)
current.left = node;
else
current.right = node;
} else {
this.root = node;
}
//3. 将它重新修整为一颗红黑树
insertFixUp(node);
}
这与二叉搜索树中实现的思路一模一样,这里不再赘述,主要看看方法里面最后一步insertFixUp操作。因为插入后可能会导致树的不平衡,insertFixUp方法里主要是分情况讨论,分析何时变色,何时左旋,何时右旋。我们先从理论上分析具体的情况,然后再看insertFixUp方法的具体实现。
如果是第一次插入,由于原树为空,所以只会违反红-黑树的规则2,所以只要把根节点涂黑即可;如果插入节点的父节点是黑色的,那不会违背红-黑树的规则,什么也不需要做;但是遇到如下三种情况时,我们就要开始变色和旋转了:
private void insertFixUp(RBNode<T> node) {
RBNode<T> parent, gparent; //定义父节点和祖父节点
//需要修整的条件:父节点存在,且父节点的颜色是红色
while(((parent = parentOf(node)) != null) && isRed(parent)) {
gparent = parentOf(parent);//获得祖父节点
//若父节点是祖父节点的左子节点,下面else与其相反
if(parent == gparent.left) {
RBNode<T> uncle = gparent.right; //获得叔叔节点
//case1: 叔叔节点也是红色
if(uncle != null && isRed(uncle)) {
setBlack(parent); //把父节点和叔叔节点涂黑
setBlack(uncle);
setRed(gparent); //把祖父节点涂红
node = gparent; //将位置放到祖父节点处
continue; //继续while,重新判断
}
//case2: 叔叔节点是黑色,且当前节点是右子节点
if(node == parent.right) {
leftRotate(parent); //从父节点处左旋
RBNode<T> tmp = parent; //然后将父节点和自己调换一下,为下面右旋做准备
parent = node;
node = tmp;
}
//case3: 叔叔节点是黑色,且当前节点是左子节点
setBlack(parent);
setRed(gparent);
rightRotate(gparent);
} else { //若父节点是祖父节点的右子节点,与上面的完全相反,本质一样的
RBNode<T> uncle = gparent.left;
//case1: 叔叔节点也是红色
if(uncle != null & isRed(uncle)) {
setBlack(parent);
setBlack(uncle);
setRed(gparent);
node = gparent;
continue;
}
//case2: 叔叔节点是黑色的,且当前节点是左子节点
if(node == parent.left) {
rightRotate(parent);
RBNode<T> tmp = parent;
parent = node;
node = tmp;
}
//case3: 叔叔节点是黑色的,且当前节点是右子节点
setBlack(parent);
setRed(gparent);
leftRotate(gparent);
}
}
//将根节点设置为黑色
setBlack(this.root);
}
3.4 删除操作
/*********************** 删除红黑树中的节点 **********************/
public void remove(T key) {
RBNode<T> node;
if((node = search(root, key)) != null)
remove(node);
}
private void remove(RBNode<T> node) {
RBNode<T> child, parent;
boolean color;
//1. 被删除的节点“左右子节点都不为空”的情况
if((node.left != null) && (node.right != null)) {
//先找到被删除节点的后继节点,用它来取代被删除节点的位置
RBNode<T> replace = node;
// 1). 获取后继节点
replace = replace.right;
while(replace.left != null)
replace = replace.left;
// 2). 处理“后继节点”和“被删除节点的父节点”之间的关系
if(parentOf(node) != null) { //要删除的节点不是根节点
if(node == parentOf(node).left)
parentOf(node).left = replace;
else
parentOf(node).right = replace;
} else { //否则
this.root = replace;
}
// 3). 处理“后继节点的子节点”和“被删除节点的子节点”之间的关系
child = replace.right; //后继节点肯定不存在左子节点!
parent = parentOf(replace);
color = colorOf(replace);//保存后继节点的颜色
if(parent == node) { //后继节点是被删除节点的子节点
parent = replace;
} else { //否则
if(child != null)
setParent(child, parent);
parent.left = child;
replace.right = node.right;
setParent(node.right, replace);
}
replace.parent = node.parent;
replace.color = node.color; //保持原来位置的颜色
replace.left = node.left;
node.left.parent = replace;
if(color == BLACK) { //4. 如果移走的后继节点颜色是黑色,重新修整红黑树
removeFixUp(child, parent);//将后继节点的child和parent传进去
}
node = null;
return;
}
}
4.完整源码
package tree;
/**
* @description implementation of Red-Black Tree by Java
* @author eson_15
* @param <T>
* @date 2016-4-18 19:27:28
*/
public class RBTree<T extends Comparable<T>> {
private RBNode<T> root; //根节点
private static final boolean RED = false; //定义红黑树标志
private static final boolean BLACK = true;
//内部类:节点类
public class RBNode<T extends Comparable<T>>{
boolean color; //颜色
T key; //关键字(键值)
RBNode<T> left; //左子节点
RBNode<T> right; //右子节点
RBNode<T> parent; //父节点
public RBNode(T key, boolean color, RBNode<T> parent, RBNode<T> left, RBNode<T> right) {
this.key = key;
this.color = color;
this.parent = parent;
this.left = left;
this.right = right;
}
public T getKey() {
return key;
}
public String toString() {
return "" + key + (this.color == RED? "R" : "B");
}
}
public RBTree() {
root = null;
}
public RBNode<T> parentOf(RBNode<T> node) { //获得父节点
return node != null? node.parent : null;
}
public void setParent(RBNode<T> node, RBNode<T> parent) { //设置父节点
if(node != null)
node.parent = parent;
}
public boolean colorOf(RBNode<T> node) { //获得节点的颜色
return node != null? node.color : BLACK;
}
public boolean isRed(RBNode<T> node) { //判断节点的颜色
return (node != null)&&(node.color == RED)? true : false;
}
public boolean isBlack(RBNode<T> node) {
return !isRed(node);
}
public void setRed(RBNode<T> node) { //设置节点的颜色
if(node != null)
node.color = RED;
}
public void setBlack(RBNode<T> node) {
if(node != null) {
node.color = BLACK;
}
}
public void setColor(RBNode<T> node, boolean color) {
if(node != null)
node.color = color;
}
/***************** 前序遍历红黑树 *********************/
public void preOrder() {
preOrder(root);
}
private void preOrder(RBNode<T> tree) {
if(tree != null) {
System.out.print(tree.key + " ");
preOrder(tree.left);
preOrder(tree.right);
}
}
/***************** 中序遍历红黑树 *********************/
public void inOrder() {
inOrder(root);
}
private void inOrder(RBNode<T> tree) {
if(tree != null) {
preOrder(tree.left);
System.out.print(tree.key + " ");
preOrder(tree.right);
}
}
/***************** 后序遍历红黑树 *********************/
public void postOrder() {
postOrder(root);
}
private void postOrder(RBNode<T> tree) {
if(tree != null) {
preOrder(tree.left);
preOrder(tree.right);
System.out.print(tree.key + " ");
}
}
/**************** 查找红黑树中键值为key的节点 ***************/
public RBNode<T> search(T key) {
return search(root, key);
// return search2(root, key); //使用递归的方法,本质一样的
}
private RBNode<T> search(RBNode<T> x, T key) {
while(x != null) {
int cmp = key.compareTo(x.key);
if(cmp < 0)
x = x.left;
else if(cmp > 0)
x = x.right;
else
return x;
}
return x;
}
//使用递归
private RBNode<T> search2(RBNode<T> x, T key) {
if(x == null)
return x;
int cmp = key.compareTo(x.key);
if(cmp < 0)
return search2(x.left, key);
else if(cmp > 0)
return search2(x.right, key);
else
return x;
}
/**************** 查找最小节点的值 **********************/
public T minValue() {
RBNode<T> node = minNode(root);
if(node != null)
return node.key;
return null;
}
private RBNode<T> minNode(RBNode<T> tree) {
if(tree == null)
return null;
while(tree.left != null) {
tree = tree.left;
}
return tree;
}
/******************** 查找最大节点的值 *******************/
public T maxValue() {
RBNode<T> node = maxNode(root);
if(node != null)
return node.key;
return null;
}
private RBNode<T> maxNode(RBNode<T> tree) {
if(tree == null)
return null;
while(tree.right != null)
tree = tree.right;
return tree;
}
/********* 查找节点x的后继节点,即大于节点x的最小节点 ***********/
public RBNode<T> successor(RBNode<T> x) {
//如果x有右子节点,那么后继节点为“以右子节点为根的子树的最小节点”
if(x.right != null)
return minNode(x.right);
//如果x没有右子节点,会出现以下两种情况:
//1. x是其父节点的左子节点,则x的后继节点为它的父节点
//2. x是其父节点的右子节点,则先查找x的父节点p,然后对p再次进行这两个条件的判断
RBNode<T> p = x.parent;
while((p != null) && (x == p.right)) { //对应情况2
x = p;
p = x.parent;
}
return p; //对应情况1
}
/********* 查找节点x的前驱节点,即小于节点x的最大节点 ************/
public RBNode<T> predecessor(RBNode<T> x) {
//如果x有左子节点,那么前驱结点为“左子节点为根的子树的最大节点”
if(x.left != null)
return maxNode(x.left);
//如果x没有左子节点,会出现以下两种情况:
//1. x是其父节点的右子节点,则x的前驱节点是它的父节点
//2. x是其父节点的左子节点,则先查找x的父节点p,然后对p再次进行这两个条件的判断
RBNode<T> p = x.parent;
while((p != null) && (x == p.left)) { //对应情况2
x = p;
p = x.parent;
}
return p; //对应情况1
}
/*************对红黑树节点x进行左旋操作 ******************/
/*
* 左旋示意图:对节点x进行左旋
* p p
* / /
* x y
* / \ / \
* lx y -----> x ry
* / \ / \
* ly ry lx ly
* 左旋做了三件事:
* 1. 将y的左子节点赋给x的右子节点,并将x赋给y左子节点的父节点(y左子节点非空时)
* 2. 将x的父节点p(非空时)赋给y的父节点,同时更新p的子节点为y(左或右)
* 3. 将y的左子节点设为x,将x的父节点设为y
*/
private void leftRotate(RBNode<T> x) {
//1. 将y的左子节点赋给x的右子节点,并将x赋给y左子节点的父节点(y左子节点非空时)
RBNode<T> y = x.right;
x.right = y.left;
if(y.left != null)
y.left.parent = x;
//2. 将x的父节点p(非空时)赋给y的父节点,同时更新p的子节点为y(左或右)
y.parent = x.parent;
if(x.parent == null) {
this.root = y; //如果x的父节点为空,则将y设为父节点
} else {
if(x == x.parent.left) //如果x是左子节点
x.parent.left = y; //则也将y设为左子节点
else
x.parent.right = y;//否则将y设为右子节点
}
//3. 将y的左子节点设为x,将x的父节点设为y
y.left = x;
x.parent = y;
}
/*************对红黑树节点y进行右旋操作 ******************/
/*
* 左旋示意图:对节点y进行右旋
* p p
* / /
* y x
* / \ / \
* x ry -----> lx y
* / \ / \
* lx rx rx ry
* 右旋做了三件事:
* 1. 将x的右子节点赋给y的左子节点,并将y赋给x右子节点的父节点(x右子节点非空时)
* 2. 将y的父节点p(非空时)赋给x的父节点,同时更新p的子节点为x(左或右)
* 3. 将x的右子节点设为y,将y的父节点设为x
*/
private void rightRotate(RBNode<T> y) {
//1. 将y的左子节点赋给x的右子节点,并将x赋给y左子节点的父节点(y左子节点非空时)
RBNode<T> x = y.left;
y.left = x.right;
if(x.right != null)
x.right.parent = y;
//2. 将x的父节点p(非空时)赋给y的父节点,同时更新p的子节点为y(左或右)
x.parent = y.parent;
if(y.parent == null) {
this.root = x; //如果x的父节点为空,则将y设为父节点
} else {
if(y == y.parent.right) //如果x是左子节点
y.parent.right = x; //则也将y设为左子节点
else
y.parent.left = x;//否则将y设为右子节点
}
//3. 将y的左子节点设为x,将x的父节点设为y
x.right = y;
y.parent = x;
}
/*********************** 向红黑树中插入节点 **********************/
public void insert(T key) {
RBNode<T> node = new RBNode<T>(key, RED, null, null, null);
if(node != null)
insert(node);
}
//将节点插入到红黑树中,这个过程与二叉搜索树是一样的
private void insert(RBNode<T> node) {
RBNode<T> current = null; //表示最后node的父节点
RBNode<T> x = this.root; //用来向下搜索用的
//1. 找到插入的位置
while(x != null) {
current = x;
int cmp = node.key.compareTo(x.key);
if(cmp < 0)
x = x.left;
else
x = x.right;
}
node.parent = current; //找到了位置,将当前current作为node的父节点
//2. 接下来判断node是插在左子节点还是右子节点
if(current != null) {
int cmp = node.key.compareTo(current.key);
if(cmp < 0)
current.left = node;
else
current.right = node;
} else {
this.root = node;
}
//3. 将它重新修整为一颗红黑树
insertFixUp(node);
}
private void insertFixUp(RBNode<T> node) {
RBNode<T> parent, gparent; //定义父节点和祖父节点
//需要修整的条件:父节点存在,且父节点的颜色是红色
while(((parent = parentOf(node)) != null) && isRed(parent)) {
gparent = parentOf(parent);//获得祖父节点
//若父节点是祖父节点的左子节点,下面else与其相反
if(parent == gparent.left) {
RBNode<T> uncle = gparent.right; //获得叔叔节点
//case1: 叔叔节点也是红色
if(uncle != null && isRed(uncle)) {
setBlack(parent); //把父节点和叔叔节点涂黑
setBlack(uncle);
setRed(gparent); //把祖父节点涂红
node = gparent; //将位置放到祖父节点处
continue; //继续while,重新判断
}
//case2: 叔叔节点是黑色,且当前节点是右子节点
if(node == parent.right) {
leftRotate(parent); //从父节点处左旋
RBNode<T> tmp = parent; //然后将父节点和自己调换一下,为下面右旋做准备
parent = node;
node = tmp;
}
//case3: 叔叔节点是黑色,且当前节点是左子节点
setBlack(parent);
setRed(gparent);
rightRotate(gparent);
} else { //若父节点是祖父节点的右子节点,与上面的完全相反,本质一样的
RBNode<T> uncle = gparent.left;
//case1: 叔叔节点也是红色
if(uncle != null & isRed(uncle)) {
setBlack(parent);
setBlack(uncle);
setRed(gparent);
node = gparent;
continue;
}
//case2: 叔叔节点是黑色的,且当前节点是左子节点
if(node == parent.left) {
rightRotate(parent);
RBNode<T> tmp = parent;
parent = node;
node = tmp;
}
//case3: 叔叔节点是黑色的,且当前节点是右子节点
setBlack(parent);
setRed(gparent);
leftRotate(gparent);
}
}
//将根节点设置为黑色
setBlack(this.root);
}
/*********************** 删除红黑树中的节点 **********************/
public void remove(T key) {
RBNode<T> node;
if((node = search(root, key)) != null)
remove(node);
}
private void remove(RBNode<T> node) {
RBNode<T> child, parent;
boolean color;
//1. 被删除的节点“左右子节点都不为空”的情况
if((node.left != null) && (node.right != null)) {
//先找到被删除节点的后继节点,用它来取代被删除节点的位置
RBNode<T> replace = node;
// 1). 获取后继节点
replace = replace.right;
while(replace.left != null)
replace = replace.left;
// 2). 处理“后继节点”和“被删除节点的父节点”之间的关系
if(parentOf(node) != null) { //要删除的节点不是根节点
if(node == parentOf(node).left)
parentOf(node).left = replace;
else
parentOf(node).right = replace;
} else { //否则
this.root = replace;
}
// 3). 处理“后继节点的子节点”和“被删除节点的子节点”之间的关系
child = replace.right; //后继节点肯定不存在左子节点!
parent = parentOf(replace);
color = colorOf(replace);//保存后继节点的颜色
if(parent == node) { //后继节点是被删除节点的子节点
parent = replace;
} else { //否则
if(child != null)
setParent(child, parent);
parent.left = child;
replace.right = node.right;
setParent(node.right, replace);
}
replace.parent = node.parent;
replace.color = node.color; //保持原来位置的颜色
replace.left = node.left;
node.left.parent = replace;
if(color == BLACK) { //4. 如果移走的后继节点颜色是黑色,重新修整红黑树
removeFixUp(child, parent);//将后继节点的child和parent传进去
}
node = null;
return;
}
}
//node表示待修正的节点,即后继节点的子节点(因为后继节点被挪到删除节点的位置去了)
private void removeFixUp(RBNode<T> node, RBNode<T> parent) {
RBNode<T> other;
while((node == null || isBlack(node)) && (node != this.root)) {
if(parent.left == node) { //node是左子节点,下面else与这里的刚好相反
other = parent.right; //node的兄弟节点
if(isRed(other)) { //case1: node的兄弟节点other是红色的
setBlack(other);
setRed(parent);
leftRotate(parent);
other = parent.right;
}
//case2: node的兄弟节点other是黑色的,且other的两个子节点也都是黑色的
if((other.left == null || isBlack(other.left)) &&
(other.right == null || isBlack(other.right))) {
setRed(other);
node = parent;
parent = parentOf(node);
} else {
//case3: node的兄弟节点other是黑色的,且other的左子节点是红色,右子节点是黑色
if(other.right == null || isBlack(other.right)) {
setBlack(other.left);
setRed(other);
rightRotate(other);
other = parent.right;
}
//case4: node的兄弟节点other是黑色的,且other的右子节点是红色,左子节点任意颜色
setColor(other, colorOf(parent));
setBlack(parent);
setBlack(other.right);
leftRotate(parent);
node = this.root;
break;
}
} else { //与上面的对称
other = parent.left;
if (isRed(other)) {
// Case 1: node的兄弟other是红色的
setBlack(other);
setRed(parent);
rightRotate(parent);
other = parent.left;
}
if ((other.left==null || isBlack(other.left)) &&
(other.right==null || isBlack(other.right))) {
// Case 2: node的兄弟other是黑色,且other的俩个子节点都是黑色的
setRed(other);
node = parent;
parent = parentOf(node);
} else {
if (other.left==null || isBlack(other.left)) {
// Case 3: node的兄弟other是黑色的,并且other的左子节点是红色,右子节点为黑色。
setBlack(other.right);
setRed(other);
leftRotate(other);
other = parent.left;
}
// Case 4: node的兄弟other是黑色的;并且other的左子节点是红色的,右子节点任意颜色
setColor(other, colorOf(parent));
setBlack(parent);
setBlack(other.left);
rightRotate(parent);
node = this.root;
break;
}
}
}
if (node!=null)
setBlack(node);
}
/****************** 销毁红黑树 *********************/
public void clear() {
destroy(root);
root = null;
}
private void destroy(RBNode<T> tree) {
if(tree == null)
return;
if(tree.left != null)
destroy(tree.left);
if(tree.right != null)
destroy(tree.right);
tree = null;
}
/******************* 打印红黑树 *********************/
public void print() {
if(root != null) {
print(root, root.key, 0);
}
}
/*
* key---节点的键值
* direction--- 0:表示该节点是根节点
* 1:表示该节点是它的父节点的左子节点
* 2:表示该节点是它的父节点的右子节点
*/
private void print(RBNode<T> tree, T key, int direction) {
if(tree != null) {
if(0 == direction)
System.out.printf("%2d(B) is root\n", tree.key);
else
System.out.printf("%2d(%s) is %2d's %6s child\n",
tree.key, isRed(tree)?"R":"b", key, direction == 1?"right":"left");
print(tree.left, tree.key, -1);
print(tree.right, tree.key, 1);
}
}
}
下面附上测试程序吧:
package test;
import tree.RBTree;
public class RBTreeTest {
private static final int a[] = {10, 40, 30, 60, 90, 70, 20, 50, 80};
private static final boolean mDebugInsert = true; // "插入"动作的检测开关(false,关闭;true,打开)
private static final boolean mDebugDelete = true; // "删除"动作的检测开关(false,关闭;true,打开)
public static void main(String[] args) {
int i, ilen = a.length;
RBTree<Integer> tree = new RBTree<Integer>();
System.out.printf("== 原始数据: ");
for(i=0; i<ilen; i++)
System.out.printf("%d ", a[i]);
System.out.printf("\n");
for(i=0; i<ilen; i++) {
tree.insert(a[i]);
// 设置mDebugInsert=true,测试"添加函数"
if (mDebugInsert) {
System.out.printf("== 添加节点: %d\n", a[i]);
System.out.printf("== 树的详细信息: \n");
tree.print();
System.out.printf("\n");
}
}
System.out.printf("== 前序遍历: ");
tree.preOrder();
System.out.printf("\n== 中序遍历: ");
tree.inOrder();
System.out.printf("\n== 后序遍历: ");
tree.postOrder();
System.out.printf("\n");
System.out.printf("== 最小值: %s\n", tree.minValue());
System.out.printf("== 最大值: %s\n", tree.maxValue());
System.out.printf("== 树的详细信息: \n");
tree.print();
System.out.printf("\n");
// 设置mDebugDelete=true,测试"删除函数"
if (mDebugDelete) {
for(i=0; i<ilen; i++)
{
tree.remove(a[i]);
System.out.printf("== 删除节点: %d\n", a[i]);
System.out.printf("== 树的详细信息: \n");
tree.print();
System.out.printf("\n");
}
}
}
}