目录
一、前言
在前面我使用图解的方式,让大家初步了解了2-3树,可以看到
2-3
树能保证在插入元素之后,树依然保持平衡状态,它的最坏情况下所有子结点 都是2-
结点,树的高度为
lgN,
相比于我们普通的二叉查找树,最坏情况下树的高度为
N
,确实保证了最坏情况下的 时间复杂度,但是2-3
树实现起来过于复杂,所以我们介绍一种
2-3
树思想的简单实现:红黑树。
红黑树主要是对
2-3
树进行编码,红黑树背后的基本思想是用标准的二叉查找树
(
完全由
2-
结点构成
)
和一些额外的信 息(
替换
3-
结点
)
来表示
2-3
树。我们将树中的链接分为两种类型:
红链接:
将两个
2-
结点连接起来构成一个
3-
结点;
黑链接:
则是
2-3
树中的普通链接
确切的说,我们将
3-
结点表示为由由一条
左斜
的红色链接
(
两个
2-
结点其中之一是另一个的左子结点
)
相连的两个
2- 结点。这种表示法的一个优点是,我们无需修改就可以直接使用标准的二叉查找树的get
方法。

二、红黑树的定义
红黑树是含有红黑链接并满足下列条件的二叉查找树:
1.
红链接均为左链接;
2.
没有任何一个结点同时和两条红链接相连;
3.
该树是完美黑色平衡的,即任意空链接到根结点的路径上的黑链接数量相同;
下面是红黑树与
2-3
树的对应关系:
三、红黑树结点API
因为每个结点都只会有一条指向自己的链接(从它的父结点指向它),我们可以在之前的
Node
结点中添加一个布 尔类型的变量color
来表示链接的颜色。如果指向它的链接是红色的,那么该变量的值为
true
,如果链接是黑色 的,那么该变量的值为false
。
在最终给大家看代码实现之前,大家有没有想过在红黑树增删修改的过程中,会不会出现一些问题,那这些问题我们应该如何处理这么问题呢?
问题就是在平衡化身上,当我们对树结构进行增删修改时,就有可能会面临破环树结构的问题,使其不满足红黑树的定义,那我们该如何解决呢?答案是,我们需要对这些情况通过旋转进行修复,让红黑树保持平衡。
1.左旋
当某个结点的左子结点为黑色,右子结点为红色,此时需要左旋。
前提: 当前结点为 h ,它的右子结点为 x ;
1. 让 x 的左子结点变为 h 的右子结点: h.right=x.left;2. 让 h 成为 x 的左子结点: x.left=h;3. 让 h 的 color 属性变为 x 的 color 属性值: x.color=h.color;4. 让 h 的 color 属性变为 RED : h.color=true;
2.右旋
当某个结点的左子结点是红色,且左子结点的左子结点也是红色,需要右旋
前提: 当前结点为 h ,它的左子结点为 x ;
右旋过程:1. 让 x 的右子结点成为 h 的左子结点: h.left = x.right;2. 让 h 成为 x 的右子结点: x.right=h;3. 让 x 的 color 变为 h 的 color 属性值: x.color = h.color;4. 让h 的 color 为 RED;
i.向单个2-结点中插入新键
一棵只含有一个键的红黑树只含有一个
2-
结点。插入另一个键后,我们马上就需要将他们旋转。
如果新键小于当前结点的键,我们只需要新增一个红色结点即可,新的红黑树和单个 3- 结点完全等价。
如果新键大于当前结点的键,那么新增的红色结点将会产生一条红色的右链接,此时我们需要通过左旋,把红色右链接变成左链接,插入操作才算完成。形成的新的红黑树依然和 3- 结点等价,其中含有两个键,一条红色链接。
ii.向底部的2-结点插入新键
用和二叉查找树相同的方式向一棵红黑树中插入一个新键,会在树的底部新增一个结点(可以保证有序性),唯一 区别的地方是我们会用红链接将新结点和它的父结点相连。如果它的父结点是一个2-
结点,那么刚才讨论的两种方式仍然适用。
3.颜色反转
当一个结点的左子结点和右子结点的
color
都为
RED
时,也就是出现了临时的
4-
结点,此时只需要把左子结点和右子 结点的颜色变为BLACK
,同时让当前结点的颜色变为
RED
即可。
i.向一棵双键树(即一个3-结点)中插入新键
这种情况有可以分为三种子情况:
1. 新键大于原树中的两个键
2. 新键小于原树中的两个键
3. 新键介于原数中两个键之间
4.根结点的颜色总是黑色
之前我们介绍结点
API
的时候,在结点
Node
对象中
color
属性表示的是父结点指向当前结点的连接的颜色,由于根结点不存在父结点,所以每次插入操作后,我们都需要把根结点的颜色设置为黑色。
i.向树底部的3-结点插入新键
假设在树的底部的一个
3-
结点下加入一个新的结点。前面我们所讲的
3
种情况都会出现。指向新结点的链接可能是 3-结点的右链接(此时我们只需要转换颜色即可),或是左链接
(
此时我们需要进行右旋转然后再转换
)
,或是中链 接(
此时需要先左旋转然后再右旋转,最后转换颜色
)
。颜色转换会使中间结点的颜色变红,相当于将它送入了父结 点。这意味着父结点中继续插入一个新键,我们只需要使用相同的方法解决即可,直到遇到一个2-
结点或者根结点 为止。

5.代码实现
public class RedBlackTree<Key extends Comparable<Key>,Value> {
//根节点
private Node root;
//记录树中元素的个数
private int N;
//红色链接
private static final boolean Red=true;
//黑色链接
private static final boolean Black=false;
// 定义一个内部类结点类
private class Node{
// 键
public Key key;
// 值
private Value value;
// 左子节点
public Node left;
// 右子节点
public Node right;
// 链接
public boolean color;
public Node(Key key,Value value,Node left,Node right,boolean color) {
this.key=key;
this.value=value;
this.left=left;
this.right=right;
this.color=color;
}
}
// 判断树中元素是否为空
public boolean isEmpty() {
return this.N==0;
}
public int size() {
return this.N;
}
private boolean isRed(Node x) {
//空结点默认是黑色链接
if(x==null) {
return false;
}
//非空结点需要判断结点color属性的值
return x.color==Red;
}
// 左旋转
private Node rotateLeft(Node h) {
//找出当前结点h的右子结点
Node x=h.right;
//找出右子结点的左子结点,并将其连接到右子结点
h.right=x.left;
//让当前结点h成为右子结点的左子结点
x.left=h;
//让当前结点h的color变成右子结点的color
x.color=h.color;
//让当前结点h的color变为RED
h.color=Red;
//返回当前结点的右子结点
return x;
}
private Node rotateRight(Node h) {
//找出当前结点h的左子结点
Node x=h.left;
//找出左子结点的右子结点,并将其连接到左子结点
h.left=x.right;
//让当前结点h成为左子结点的右子结点
x.right=h;
//让当前结点h的color变成左子结点的color
x.color=h.color;
//让当前结点h的color变为RED
h.color=Red;
//返回当前结点的左子结点
return x;
}
// 颜色反转
private void flipColors(Node x) {
//当前结点的color属性值变为RED;
x.color=Red;
// //当前结点的左右子结点的color属性值都变为黑色
x.left.color=Black;
x.right.color=Black;
}
// 向整棵树插入(添加)
public void put(Key key,Value value) {
//在root整个树上插入key-value
root=put(root,key,value);
//让根结点的颜色变为BLACK
root.color=Black;
}
// 在指定树中,完成插入操作,并返回添加元素后新的树
public Node put(Node h,Key key,Value value) {
// 如果h结点为null,则创建一个新结点,并将其返回
if(h==null) {
// 数目+1;
N++;
return new Node(key,value,null,null,Red);
}
int cmp=key.compareTo(h.key);
//比较要插入的键和当前结点的键
if(cmp<0) {
//继续寻找左子树插入
h.left=put(h.left,key,value);
}else if(cmp>0) {
//继续寻找右子树插入
h.right=put(h.right,key,value);
}else {
//已经有相同的结点存在,修改节点的值;
h.value=value;
}
//如果当前结点的右链接是红色,左链接是黑色,需要左旋
if(isRed(h.right)&&isRed(h.left)) {
h=rotateLeft(h);
}
//如果当前结点的左子结点和左子结点的左子结点都是红色链接,则需要右旋
if(isRed(h.left)&&isRed(h.left.left)) {
h=rotateRight(h);
}
//如果当前结点的左链接和右链接都是红色,需要颜色变换
if(isRed(h.left)&&isRed(h.right)) {
flipColors(h);
}
return h;
}
//根据key,从树中找出对应的值
public Value get(Key key) {
return get(root,key);
}
//从指定的树x中,查找key对应的值
private Value get(Node h,Key key) {
//如果当前结点为空,则没有找到,返回null
if(h==null) {
return null;
}
//比较当前结点的键和key
int cmp=key.compareTo(h.key);
if(cmp<0) {
//如果要查询的key小于当前结点的key,则继续找当前结点的左子结点
return get(h.left,key);
}else if(cmp>0) {
//如果要查询的key大于当前结点的key,则继续找当前结点的右子结点;
return get(h.right,key);
}else {
//如果要查询的key等于当前结点的key,则树中返回当前结点的value。
return h.value;
}
}
}
活动地址:优快云21天学习挑战赛