1.什么是红黑树
红黑树是一种自平衡的二叉树
2.红黑树与234树的关系
红黑树与234树是有等价关系的,每一个红黑树都对应这一个234树,而每一个234树则对应着多个红黑树
2.1.什么是234树呢
234树是阶为4的B树,是一个由叶子节点向根部生长的树,正因为是由叶子向根部生长,所以234树是一颗永远平衡的树。
2.2. 234树的节点
234树一共是三种节点。
2节点:包含一个元素2个子节点。
3节点:包含两个元素3个子节点。
4节点:包含三个元素4个子节点。
2.3. 234树的生长
234树节点的增加会先从叶子节点开始,将一个2节点成长到4节点,才会向上分裂一层。就像下面的图:
1现在是根节点,增加一个2变成了3节点,再增加一个3变成了四节点,这时候是一个满节点(不能再直接添加元素)。相要再直接增加节点是不行的,这个时候只能4节点分裂,中间的2向上成为父节点,1、3各自成为其的左右孩子,4因为比3,3只是2节点,所以将3、4放在一起成为3节点。
2.4.红黑树与234树的等价关系
上面说了234树一共有3种节点,而每一种节点都可以等价与红黑树种的一种情况:
2节点:单独的叶子节点(黑色)
3节点:一父一子(上黑下红)
4节点:一父两子(上黑下红)
而上面的234树则可以转化成一种红黑树
2.5.从234树的角度理解红黑树的五大性质
1、节点必须是红黑或者黑色
2、根节点必须是黑色
3、叶子节点(NIL)是黑色。(NIL节点无数据,是空节点)
4、红色节点必须有两个黑色儿子节点
5、从任一节点出发到其每个叶子节点的路径,黑色节点的数量相当(黑色平衡)
1、节点必须是红黑或者黑色。
2、根节点必须是黑色。
234树只有三种节点,而每种节点转换成红黑树就是:单节点(黑色),双节点(上黑下红),三节点(父节点黑,两儿子是红),同样这也能证明第二个性质,因为234树每个节点转换成红黑树,无论是234树的2节点、3节点或者4节点在根节点,转换成红黑树,都是黑色在上面,所以无论怎样,根节点一定是黑色。
3、叶子节点(NIL)是黑色。(NIL节点无数据,是空节点)。
这里说的叶子节点其实是人为加上去的,是不存在,那既然是认为加上去的,为什么要定义为黑色呢?其实从性质2可以反推:当这颗红黑树不存在实际的节点,那是不是只能为空呢,那这个时候根节点就是空,再因为根节点必须是黑色,所以叶子节点(NIL)必须是黑色。
4、红色节点必须有两个黑色儿子节点。
从234树来看,每个节点都会从2节点成长成4节点,每一种节点都是上黑下红,4节点两个红色的子节点下面连的必定是黑色的节点。当这个红色节点没有子节点是,他还有一个为NIL的空节点,由性质3空节点必须是黑色,也能证明红色节点必须有两个黑色的儿子节点。
5、从任一节点出发到其每个叶子节点的路径,黑色节点的数量相当(黑色平衡)
这个性质也就是红黑树自平衡的一个重要性质了,红黑树平衡一直指的是黑色平衡,为什么黑色永远是平衡的?
从234树来看,234树是一个永远平衡的树,因为他是自下而上生长的。而他只有三种节点,2节点、3节点、4节点转化成红黑树的节点都是有且仅有一个黑色节点,正因为234树从任一节点出发到每一个叶子节点的路径的节点都是相等的,而这些节点转换成红黑树时,每一个节点都只能转化出一个黑色节点,所以红黑树从任一节点出发到其每一个叶子节点的路径上,黑色节点的数量都是相等的。
2.3.java代码实现红黑树
该部分代码是模仿TreeMap源码加上自己理解,作了简化写的。
2.3.1.基本变量定义
import java.io.Serializable;
public class RedBlackTree<K extends Comparable<K>, V> implements Serializable {
/** 红色节点 */
private static final boolean RED = false;
/** 黑色节点*/
private static final boolean BLACK = true;
/** 根节点 */
private Node<K, V> root;
/** 获取该节点的左子节点 */
private static <K, V> Node<K, V> leftOf(Node<K, V> x) {
return (x == null) ? null : x.left;
}
/** 获取该节点的右子节点 */
private static <K, V> Node<K, V> rightOf(Node<K, V> x) {
return (x == null) ? null : x.right;
}
/** 获取该节点的父节点 */
private static <K, V> Node<K, V> parentOf(Node<K, V> x) {
return x == null ? null : x.parent;
}
/** 获取该节点的颜色 */
private static <K, V> boolean colorOf(Node<K, V> x) {
// 节点不存在时为Nil节点,Nil节点默认为黑色
return x == null ? BLACK : x.color;
}
/** 设置节点颜色 */
private static <K,V> void setColor(Node<K,V> x, boolean c) {
if (x != null) {
x.color = c;
}
}
public Node<K, V> getRoot() {
return root;
}
static final class Node<K, V> {
K key;
V value;
Node<K, V> left;
Node<K, V> right;
Node<K, V> parent;
boolean color = BLACK;
public Node(K key, V value, Node<K, V> parent) {
this.key = key;
this.value = value;
this.parent = parent;
}
public K getKey() {
return key;
}
public void setKey(K key) {
this.key = key;
}
public V getValue() {
return value;
}
public void setValue(V value) {
this.value = value;
}
public Node<K, V> getLeft() {
return left;
}
public void setLeft(Node<K, V> left) {
this.left = left;
}
public Node<K, V> getRight() {
return right;
}
public void setRight(Node<K, V> right) {
this.right = right;
}
public Node<K, V> getParent() {
return parent;
}
public void setParent(Node<K, V> parent) {
this.parent = parent;
}
public boolean isColor() {
return color;
}
public void setColor(boolean color) {
this.color = color;
}
}
}
在了解红黑树怎么插入和删除节点之前,得先知道左旋右旋是怎样的。
2.3.2.左旋
左旋主要有三个关键点:(以要左旋的节点为X举例说明)
1、x节点是否存在右子节点,不存在则不能左旋
2、x节点的父节点要成为其右子节点的父节点
3、x节点的右子节点(xr)的左子节点(rl)要成为x节点新的右子节点
/**
* 左旋概念: 将指定x节点左旋,是以x节点的右孩子为轴心,x节点向左旋转,x成为其右孩子的左孩子,其右孩子成为x节点的父节点,
* x节点的原始父节点成为其原始右孩子的父节点, x节点的原始右孩子的左孩子(原始孙子节点)成为x节点的右孩子,其余节点保持不变
* 左旋图形演示
* p p
* / /
* x xr
* / \ / \
* xl xr ==> x rr
* / \ / \
* rl rr xl rl
*
* @param x 需要左旋的节点
*/
private void leftRotate(Node<K, V> x) {
Node<K, V> xr;
if (x != null && (xr = x.right) != null) {
// x节点的右子节点的左节点,赋值给x节点的右子节点(x的孙子成了儿子)
Node<K, V> rl = x.right = xr.left;
if (rl != null) {
// 孙子节点若存在,改变孙子节点的父节点(rl的爷爷成了爸爸)
rl.parent = x;
}
// x节点的父节点赋值给右子节点的父节点(xr的爷爷成了爸爸)
Node<K, V> xp = xr.parent = x.parent;
if (xp == null) {
// x不存在父节点,即x就是根节点,故让xr成为新根节点
this.root = xr;
} else if (x == leftOf(xp)) {
// xr替代x成为爷爷节点的左子节点
xp.left = xr;
} else {
// xr替代x成为爷爷节点的右子节点
xp.right = xr;
}
// x成为其右孩子的左孩子
xr.left = x;
// 其右孩子成为x节点的父节点
x.parent = xr;
}
}
2.3.3.右旋
右旋和左旋是一样的,只是方向全部都得反过来
右旋的三个关键点:(以要右旋的节点为X举例说明)
1、x节点是否存在左子节点,不存在则不能右旋
2、x节点的父节点要成为其左子节点的父节点
3、x节点的左子节点(xl)的右子节点(lr)要成为x节点新的左子节点
/**
* 右旋概念: 将指定x节点左旋,是以x节点的左孩子为轴心,x节点向右旋转,x成为其左孩子的右孩子,其左孩子成为x节点的父节点,
* x节点的原始父节点成为其原始左孩子的父节点, x节点的原始左孩子的右孩子(原始孙子节点)成为x节点的左孩子,其余节点保持不变
* 右旋图形
* x xl
* / \ / \
* xl xr ==> ll x
* / \ / \
* ll lr lr xr
* @param x 需要旋转的节点
*/
private void rightRotate(Node<K, V> x) {
Node<K, V> xl;
if (x != null && (xl = x.left) != null) {
// x节点的左孩子的右孩子,成为x节点的左孩子(x的孙子成为儿子)
Node<K, V> lr = x.left = xl.right;
if (lr != null) {
// 孙子节点若存在,改变孙子节点的父节点(lr的爷爷成了爸爸)
lr.parent = x;
}
// x节点的父节点赋值给左子节点的父节点(xl的爷爷成了爸爸)
Node<K, V> xp = xl.parent = x.parent;
if (xp == null) {
// x不存在父节点,即x就是根节点,故让xl成为新根节点
this.root = xl;
} else if (x == rightOf(xp)){
// xl替代x成为爷爷节点的右子节点
xp.right = xl;
} else {
// xl替代x成为爷爷节点的左子节点
xp.left = xl;
}
// x成为其左孩子的右孩子
xl.right = x;
// 其左孩子成为x节点的父节点
x.parent = xl;
}
}
2.3.4.增加节点
新增节点时,需要满足红黑树的五大特性:特性1、2、3不需要刻意调整,本来就是满足的,主要是调整4和5特性,然而同时满足4和5是很困难的,如果新加入的节点是黑色,特性4肯定满足,特性5则肯定被破坏,多了一个黑色节点,无论是放在哪个分支,黑色都多了一个。而如果新加入的节点是红色,则特性五就满足了,接下来只需要满足特性4就行了。所以平衡红黑树,对于新加入的节点默认为红色。
新增节点首先考虑新增节点一共哪些情况:(默认新增加节点为红色)
1、根节点(红黑树此时不存在根节点),直接将新增节点变黑即可
2、新增节点存在父节点(不为根节点),且父节点为黑色,不需要做处理。(对应234树的2、3节点,才会出现父节点为黑色)
3、新增节点存在父节点(不为根节点),父节点为红色,不存在叔叔节点。(对应234树的3节点),不满足特性4需要调整
4、新增节点存在父节点(不为根节点),父节点为红色,存在叔叔节点。(对应234树的4节点),不满足特性4需要调整
从234树的3、4节点可以看出,存在叔叔节点时,叔叔节点肯定为红色(后面代码会用,TreeMap是用叔叔节点是否为红色,判断叔叔节点是否存在的)。
新增节点
/**
* 根据插入key的值检索插入的位置,并平衡红黑树
* @param key key
* @param value value
* @return 该key的原始值,不存在为null
*/
public V put(K key, V value) {
if (key == null) {
throw new NullPointerException();
}
Node<K, V> t = this.root;
if (t == null) {
// 红黑树种不存在根节点,将存入的值存为根节点
this.root = new Node<>(key, value, null);
// 原始根节点不存在值,故返回null
return null;
}
Node<K, V> parent;
int cmp;
do {
parent = t;
cmp = key.compareTo(t.key);
if (cmp >0) {
// 将t的右节点赋值给t,继续向右检索
t = t.right;
} else if (cmp < 0) {
// 将t的左节点赋值给t,继续向左检索
t =t.left;
} else {
// 该key已存在,替换值就行
V v = t.value;
t.value = value;
return v;
}
// t已检索到叶子节点
} while (t != null);
Node<K, V> e = new Node<>(key, value, parent);
// 判断key在parent的左边还是右边
if (cmp > 0) {
parent.right = e;
} else {
parent.left = e;
}
// 平衡红黑树
fixAfterInsertion(e);
return null;
}
平衡新增节点的颜色
/**
* 新加入节点情况统计:
* 1.插入的节点是第一个节点(根节点) : x 直接变黑
* 2.插入的节点存在父节点和叔叔节点并且叔叔节点为红色 :
* 无论x是为左节点还是右节点情况都一样处理,父节点为黑色不做处理,
* 父节点为红色,父节点和叔叔节点变成黑色,爷爷节点变成红色,爷爷节点赋值给x进行递归
* pp
* / \
* p u
* /
* x
* 3.插入的节点不存在叔叔节点:
* (1) pp (2) pp (3) pp (4) pp
* / / \ \
* p p p p
* / \ / \
* x x x x
* 仅有上述四种情况1,2 与3,4只是父节点在左与右的区别 , 而2可以通过左旋变成1 ,同理3可以通过右旋变成4
* @param x 需要平衡的节点
*/
private void fixAfterInsertion(Node<K, V> x) {
// 新存入的值,直接赋值为红色
x.color = RED;
// x不为根节点(根节点直接赋值为黑色即可), x父节点为黑色代表红黑树此时是平衡的(红色节点不影响红黑树的黑色平衡)
while (x != null && x != root && x.parent.color == RED) {
// x父节点为爷爷节点的左孩子
if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
// 爷爷节点的右孩子,就是叔叔节点
Node<K, V> u = rightOf(parentOf(parentOf(x)));
// 叔叔节点为红色,因为该方法在节点为空也为黑色,即这里为true,叔叔节点肯定存在
if (colorOf(u) == RED) {
setColor(u, BLACK);
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
x = parentOf(parentOf(x));
} else {
// x节点是父节点的右孩子,需要左旋换到一致
if (x == rightOf(parentOf(x))) {
// 将父节点赋值给x,以x左旋之后,x再次成为了左孩子
x = parentOf(x);
leftRotate(x);
}
// 父节点变成黑色
setColor(parentOf(x), BLACK);
// 爷爷节点变成红色
setColor(parentOf(parentOf(x)), RED);
// 爷爷节点右旋(对应第三种情况的第一个小情况), 旋转完成之后,父节黑色节点替代爷爷节点,因为爷爷节点以前也是黑色,
// 不会对上面节点造成影响
rightRotate(parentOf(parentOf(x)));
}
} else {
// 爷爷节点的左孩子,就是叔叔节点
Node<K, V> u = leftOf(parentOf(parentOf(x)));
// 叔叔节点为红色,因为该方法在节点为空也为黑色,即这里为true,叔叔节点肯定存在
if (colorOf(u) == RED) {
setColor(parentOf(x), BLACK);
setColor(u, BLACK);
setColor(parentOf(parentOf(x)), RED);
x = parentOf(parentOf(x));
} else {
// x节点是父节点的左孩子,需要右旋换到一致
if (x == leftOf(parentOf(x))) {
x = parentOf(x);
rightRotate(x);
}
// 父节点变成黑色
setColor(parentOf(x), BLACK);
// 爷爷节点变成红色
setColor(parentOf(parentOf(x)), RED);
// 爷爷节点左旋(对应第三种情况的第四个小情况), 旋转完成之后,父节黑色点替代爷爷节点,因为爷爷节点以前也是黑色,
// 不会对上面节点造成影响
leftRotate(parentOf(parentOf(x)));
}
}
}
root.color = BLACK;
}
2.3.5.前驱节点和后继节点
在了解删除节点前,需要先了解两个概念,在删除时会用到:
- 前驱节点:小于当前节点的最大值
- 后继节点:大于当前节点的最小值
代码实现:
前驱节点
/**
* 前驱节点指的是小于当前节点的最大值
* 寻找小于当前节点的最大值有三条路:
* 1,右子节点(只能一直增大,不行)
* 2,左子节点(找到当前节点的左子节点,然后一直向右寻找)
* 3,父节点(这种在红黑树删除用不到, 这种要么自己本身就是叶子节点直接删除,或者有一个子节点,直接子节点替换掉指定节点即可)
* 3.1 指定节点为父节点的右子节点,父节点即是前驱节点
* 3.2 指定节点为父节点的左子节点,一直向上寻找,直到找到一个父节点是爷爷节点的右孩子
*
* @param x x
* @return node 或者 null
*/
private Node<K, V> precursor(Node<K, V> x) {
if (x == null) {
return null;
// 指定节点存在左子节点
} else if (x.left != null) {
Node<K, V> l = x.left;
// 左子节点的右子节点不为空的情况,一直循环下去,直到找到前驱节点
while (l.right != null) {
l = l.right;
}
return l;
} else {
// 定义变量保存父节点和子节点
Node<K, V> parent = x.parent;
Node<K, V> child = x;
// 父节点为空的情况,即没有前驱节点,返回null, 或者还回前驱节点
while (parent!= null && child == parent.left) {
child = parent;
parent = parent.parent;
}
return parent;
}
}
后继节点
/**
* 后继节点指大于当前节点的最小值
* 寻找大于当前节点的最小值有三条路:
* 1,左子节点(只能一直减小,不行)
* 2,右子节点(找到当前节点的右子节点,然后一直向左寻找)
* 3,父节点:(这种在红黑树删除用不到, 这种要么自己本身就是叶子节点直接删除,或者有一个子节点,直接子节点替换掉指定节点即可)
* 3.1,指定节点为父节点的左子节点,父节点即是后继节点
* 3.2,指定节点为父节点的右子节点,一直向上寻找,直到找到一个父节点是爷爷节点的左孩子
*
* @param x x
* @return node 或 null
*/
private Node<K, V> successor(Node<K, V> x) {
if (x == null) {
return null;
// 指定节点的右子节点
} else if (x.right != null) {
Node<K, V> r = x.right;
// 右子节点的左子节点不为空的情况,一直循环下去,直到找到后继节点
while (r.left != null) {
r = r.left;
}
return r;
} else {
// 定义变量保存父节点和子节点
Node<K, V> parent = x.parent;
Node<K, V> child = x;
// 父节点为空的情况,即没有前驱节点,返回null, 或者还回前驱节点
while (parent != null && child == parent.right) {
child = parent;
parent = parent.parent;
}
return parent;
}
}
2.3.6.删除节点
1、删除的节点为叶子节点时(这里不是指根节点和NIL节点):
- 红色:直接删除
- 黑色:这时兄弟节点肯定存在,不存在红黑树不能平衡,需要判断兄弟节点是否为黑色,若为黑色,则兄弟节点变成红色,父节点为红色时,染黑;父节点为黑色时,将父节点变成需要平衡的节点,继续循环这个步骤,直到找到有一个父节点为红色。
2、删除的节点存在一个子节点:
- 红色:直接删除,子节点代替该节点
- 黑色:若子节点为红色,子节点变成黑色代替;若子节点为黑色,则进行第一种情况里,节点为黑色的步骤。
3、删除的节点存在两个子节点:
红黑树中删除,如果存在两个节点就可以用前驱节点或者后继节点来替换需要删除的对象
后继节点8代替
前驱节点6代替
这里代替是将前驱节点或后继节点的值替换到要删除的节点,这时再根据前驱节点是否右子节点进入到情况1或者情况2删除前驱节点或者后继节点。(为什么不进入到情况3,因为如果能进入到情况3,他在一开始就不是前驱节点或者后继节点)
代码实现
/**
* 根据key移除指定的节点,若节点存在返回节点的值,若不存在还回null
* @param key key
* @return value 或 null
*/
public V remove(K key) {
Node<K, V> p = getNode(key);
if (p == null) {
return null;
}
V oldValue = p.value;
deleteNode(p);
return oldValue;
}
/**
* 根据key值获取指定节点
* @param key key
* @return Node 或者 null
*/
private Node<K, V> getNode(K key) {
if (key == null) {
return null;
}
// 获取root节点,从root开始查找
Node<K, V> p = this.root;
// 定义比较变量,存储k与p节点比较的值
int cmp;
while (p != null){
// 比较key与p的大小
cmp = key.compareTo(p.key);
// key比p节点大
if (cmp > 0) {
// p节点的右子节点赋值给p,继续往右查找
p = p.right;
// key比p节点小
} else if (cmp < 0) {
// p节点的左子节点赋值给p,继续往左查找
p =p.left;
} else {
// cmp = 0,找到key就是p节点
return p;
}
}
return null;
}
/**
* 删除指定节点:
* 删除指定节点的三种情况
* 1.指定节点为叶子节点
* 若为红色直接删除;
* 若为黑色,将兄弟节点变成红色,将父节点为红色时变成黑色;若父节点为黑色,继续向上寻找,直到找到红色的父节点
* 2.指定节点带有一个子节点,删掉指定节点,用其子节点替换
* 3.指定节点带有两个子节点,为保证红黑树平衡不被破坏,一般选用指定节点的前驱节点或者后继节点替换掉指定节点,再删除其前驱节点或者后继节点,
* 这种情况会造成指定节点断开自己的对应连接(父节点与子节点),前驱节点或后继节点断掉自己的对应连接(父节点与子节点),然后前驱节点或后继节点再与
* 指定节点的父节点与子节点建立连接,代码实现过于繁琐;
* 故,可将其简化为,将指定节点的前驱节点或后继节点的key与value直接赋值给指定节点,再删除其前驱节点或者后继节点
*
* @param p p
*/
private void deleteNode(Node<K, V> p) {
// 第三种情况,左右子节点皆存在
if (p.left != null && p.right != null) {
// 取后继节点替换指定节点
Node<K, V> sp = precursor(p);
p.key = sp.key;
p.value = sp.value;
// 将后继节点赋值给p,为了跟后面删除不同时存在左右节点的情况统一
p = sp;
}
// 找到p节点的子节点,可能是一个,也可能是0个
Node<K, V> replace = p.left != null ? p.left : p.right;
// replace不为空,即存在一个子节点,需要用子节点替换p节点
if (replace != null) {
// 将p节点的父节点变成replace的父节点
Node<K, V> parent = p.parent;
replace.parent = parent;
// 父节点为空,即p为根节点,将replace设置成根节点
if (parent == null) {
root = replace;
// p是父节点的左节点,将replace设置成父节点的左节点
} else if (p == parent.left){
parent.left = replace;
// p是父节点的右节点,将replace设置成父节点的右节点
} else {
parent.right = replace;
}
//
p.parent = p.left = p.right = null;
// p的颜色是红色,即此时树是平衡的,不需要操作
if (p.color == BLACK) {
// p的颜色是黑色,此时需要平衡树
fixAfterDelete(replace);
}
// p节点为根节点,仅有此一个节点
} else if (p.parent == null) {
root = null;
// p节点不存在子节点,也有父节点,则断开父节点指向p节点的引用
} else {
if (p.color == BLACK) {
fixAfterDelete(p);
}
// 有父节点,需要断开与父节点的引用
if (p.parent != null) {
// p是父节点的左子节点
if (p == leftOf(parentOf(p))) {
parentOf(p).left = null;
} else {
// p是父节点的右子节点
parentOf(p).right = null;
}
// 断开p指向父节点的引用
p.parent = null;
}
}
}
/**
* 根据deleteNode的转换,将要删除的节点都转换到叶子节点或只存在一个子节点的情况(删除的节点为黑色才会进入这个方法,
* 子节点为红色直接设置为黑色,子节点为黑色即可当成即将要删除的黑色叶子平衡红色树)
*
* 1.
*
* @param x x
*/
private void fixAfterDelete(Node<K, V> x) {
while (x != root && x.color == BLACK) {
if (x == leftOf(parentOf(x))) {
Node<K, V> rNode = rightOf(parentOf(x));
// 兄弟节点为红色,不是真正的兄弟节点,x节点是父节点的左节点,通过父节点左旋,找到兄弟节点, 父节点要变成红色,右节点变成黑色(相当于234树的3节点互换位置)
if (rNode.color == RED) {
setColor(parentOf(x), RED);
setColor(rNode, BLACK);
leftRotate(parentOf(x));
rNode = rightOf(parentOf(x));
}
// 等价于((rNode != null) && (rNode.left == null) && (rNode.right == null)) || ((rNode != null) && (rNode.left.color == BLACK) && (rNode.right.color == BLACK)) ,目的是判断兄弟节点的左右孩子是否存在或者兄弟节点的两个孩子为黑色
// 不存在或者为黑色代表兄弟节点也不能借出子节点,需要兄弟节点也变成红色,继续下一轮循环(下一轮循环会判断父节点颜色,为红色则直接染黑,为黑色则继续循环)
if (colorOf(leftOf(rNode)) == BLACK &&
colorOf(rightOf(rNode)) == BLACK) {
// 将兄弟节点变成红色,让父节点继续往上平衡,若父节点为红色,则直接变成黑色;若为黑色,继续循环
setColor(rNode, RED);
// 父节点赋值给x
x = parentOf(x);
} else {
// 当兄弟节点的右子节点为空时,代表左子节点必定不为空,直接左旋父节点,会造成兄弟节点没有右孩子,直接把子节点给x节点,会影响大小排序,
// 所以必须先右旋兄弟节点,保证其有右孩子
if (colorOf(rightOf(rNode)) == BLACK) {
// 这种情况是234树3节点的情况,兄弟节点黑色,其子节点为红色,右旋的同时,颜色需要互换
setColor(rNode, RED);
setColor(leftOf(rNode), BLACK);
rightRotate(rNode);
// 重新获取新的兄弟节点
rNode = rightOf(parentOf(x));
}
// 因为兄弟节点要左旋顶替父节点的位置,需要将父节点的颜色赋值给兄弟节点
setColor(rNode, colorOf(parentOf(x)));
// 因为父节点需要顶替x节点的位置,需要将父节点染黑
setColor(parentOf(x), BLACK);
// 兄弟节点的右子节点需要顶替兄弟节点的位置,需要将其染黑
setColor(rightOf(rNode), BLACK);
// 左旋
leftRotate(parentOf(x));
// 跳出循环
x = root;
}
} else {
Node<K, V> lNode = leftOf(parentOf(x));
// 兄弟节点为红色,不是真正的兄弟节点,x节点是父节点的右节点,通过父节点右旋,找到兄弟节点, 父节点要变成红色,右节点变成黑色(相当于234树的3节点互换位置)
if (lNode.color == RED) {
setColor(parentOf(x), RED);
setColor(lNode, BLACK);
rightRotate(x);
lNode = leftOf(parentOf(x));
}
// 等价于((rNode != null) && (rNode.left == null) && (rNode.right == null)) || ((rNode != null) && (rNode.left.color == BLACK) && (rNode.right.color == BLACK)) ,目的是判断兄弟节点的左右孩子是否存在或者兄弟节点的两个孩子为黑色
// 不存在或者为黑色代表兄弟节点也不能借出子节点,需要兄弟节点也变成红色,继续下一轮循环(下一轮循环会判断父节点颜色,为红色则直接染黑,为黑色则继续循环)
if (colorOf(leftOf(lNode)) == BLACK &&
colorOf(rightOf(lNode)) == BLACK) {
// 将兄弟节点变成红色,让父节点继续往上平衡,若父节点为红色,则直接变成黑色;若为黑色,继续循环
setColor(lNode, RED);
// 父节点赋值给x
x = parentOf(x);
} else {
// 当兄弟节点的左子节点为空时,代表右子节点必定不为空,直接右旋父节点,会造成兄弟节点没有左孩子,直接把子节点给x节点,会影响大小排序,
// 所以必须先左旋兄弟节点,保证其有左孩子
if (colorOf(leftOf(lNode)) == BLACK) {
// 这种情况是234树3节点的情况,兄弟节点黑色,其子节点为红色,右旋的同时,颜色需要互换
setColor(lNode, RED);
setColor(rightOf(lNode), BLACK);
leftRotate(lNode);
// 重新获取新的兄弟节点
lNode = leftOf(parentOf(x));
}
// 因为兄弟节点要右旋顶替父节点的位置,需要将父节点的颜色赋值给兄弟节点
setColor(lNode, colorOf(parentOf(x)));
// 因为父节点需要顶替x节点的位置,需要将父节点染黑
setColor(parentOf(x), BLACK);
// 兄弟节点的左子节点需要顶替兄弟节点的位置,需要将其染黑
setColor(leftOf(lNode), BLACK);
// 右旋
rightRotate(parentOf(x));
// 跳出循环
x = root;
}
}
}
// x节点为红色,直接染黑
setColor(x, BLACK);
}
自此红黑树的增删查,已用java代码全部实现
下面为整个类的代码,与上面是重复的,方便复制:
import java.io.Serializable;
/**
* @author jun195
* @date 2021/4/24 22:36
*/
public class RedBlackTree<K extends Comparable<K>, V> implements Serializable {
/** 红色节点 */
private static final boolean RED = false;
/** 黑色节点*/
private static final boolean BLACK = true;
private Node<K, V> root;
/** 获取该节点的左子节点 */
private static <K, V> Node<K, V> leftOf(Node<K, V> x) {
return (x == null) ? null : x.left;
}
/** 获取该节点的右子节点 */
private static <K, V> Node<K, V> rightOf(Node<K, V> x) {
return (x == null) ? null : x.right;
}
/** 获取该节点的父节点 */
private static <K, V> Node<K, V> parentOf(Node<K, V> x) {
return x == null ? null : x.parent;
}
/** 获取该节点的颜色 */
private static <K, V> boolean colorOf(Node<K, V> x) {
// 节点不存在时为Nil节点,Nil节点默认为黑色
return x == null ? BLACK : x.color;
}
/** 设置节点颜色 */
private static <K,V> void setColor(Node<K,V> x, boolean c) {
if (x != null) {
x.color = c;
}
}
/**
* 根据插入key的值检索插入的位置,并平衡红黑树
* @param key key
* @param value value
* @return 该key的原始值,不存在为null
*/
public V put(K key, V value) {
if (key == null) {
throw new NullPointerException();
}
Node<K, V> t = this.root;
if (t == null) {
// 红黑树种不存在根节点,将存入的值存为根节点
this.root = new Node<>(key, value, null);
// 原始根节点不存在值,故返回null
return null;
}
Node<K, V> parent;
int cmp;
do {
parent = t;
cmp = key.compareTo(t.key);
if (cmp >0) {
// 将t的右节点赋值给t,继续向右检索
t = t.right;
} else if (cmp < 0) {
// 将t的左节点赋值给t,继续向左检索
t =t.left;
} else {
// 该key已存在,替换值就行
V v = t.value;
t.value = value;
return v;
}
// t已检索到叶子节点
} while (t != null);
Node<K, V> e = new Node<>(key, value, parent);
// 判断key在parent的左边还是右边
if (cmp > 0) {
parent.right = e;
} else {
parent.left = e;
}
// 平衡红黑树
fixAfterInsertion(e);
return null;
}
/**
* 根据key移除指定的节点,若节点存在返回节点的值,若不存在还回null
* @param key key
* @return value 或 null
*/
public V remove(K key) {
Node<K, V> p = getNode(key);
if (p == null) {
return null;
}
V oldValue = p.value;
deleteNode(p);
return oldValue;
}
/**
* 删除指定节点:
* 删除指定节点的三种情况
* 1.指定节点为叶子节点
* 若为红色直接删除;
* 若为黑色,将兄弟节点变成红色,将父节点为红色时变成黑色;若父节点为黑色,继续向上寻找,直到找到红色的父节点
* 2.指定节点带有一个子节点,删掉指定节点,用其子节点替换
* 3.指定节点带有两个子节点,为保证红黑树平衡不被破坏,一般选用指定节点的前驱节点或者后继节点替换掉指定节点,再删除其前驱节点或者后继节点,
* 这种情况会造成指定节点断开自己的对应连接(父节点与子节点),前驱节点或后继节点断掉自己的对应连接(父节点与子节点),然后前驱节点或后继节点再与
* 指定节点的父节点与子节点建立连接,代码实现过于繁琐;
* 故,可将其简化为,将指定节点的前驱节点或后继节点的key与value直接赋值给指定节点,再删除其前驱节点或者后继节点
*
* @param p p
*/
private void deleteNode(Node<K, V> p) {
// 第三种情况,左右子节点皆存在
if (p.left != null && p.right != null) {
// 取后继节点替换指定节点
Node<K, V> sp = precursor(p);
p.key = sp.key;
p.value = sp.value;
// 将后继节点赋值给p,为了跟后面删除不同时存在左右节点的情况统一
p = sp;
}
// 找到p节点的子节点,可能是一个,也可能是0个
Node<K, V> replace = p.left != null ? p.left : p.right;
// replace不为空,即存在一个子节点,需要用子节点替换p节点
if (replace != null) {
// 将p节点的父节点变成replace的父节点
Node<K, V> parent = p.parent;
replace.parent = parent;
// 父节点为空,即p为根节点,将replace设置成根节点
if (parent == null) {
root = replace;
// p是父节点的左节点,将replace设置成父节点的左节点
} else if (p == parent.left){
parent.left = replace;
// p是父节点的右节点,将replace设置成父节点的右节点
} else {
parent.right = replace;
}
//
p.parent = p.left = p.right = null;
// p的颜色是红色,即此时树是平衡的,不需要操作
if (p.color == BLACK) {
// p的颜色是黑色,此时需要平衡树
fixAfterDelete(replace);
}
// p节点为根节点,仅有此一个节点
} else if (p.parent == null) {
root = null;
// p节点不存在子节点,也有父节点,则断开父节点指向p节点的引用
} else {
if (p.color == BLACK) {
fixAfterDelete(p);
}
// 有父节点,需要断开与父节点的引用
if (p.parent != null) {
// p是父节点的左子节点
if (p == leftOf(parentOf(p))) {
parentOf(p).left = null;
} else {
// p是父节点的右子节点
parentOf(p).right = null;
}
// 断开p指向父节点的引用
p.parent = null;
}
}
}
/**
* 前驱节点指的是小于当前节点的最大值
* 寻找小于当前节点的最大值有三条路:
* 1,右子节点(只能一直增大,不行)
* 2,左子节点(找到当前节点的左子节点,然后一直向右寻找)
* 3,父节点(这种在红黑树删除用不到, 这种要么自己本身就是叶子节点直接删除,或者有一个子节点,直接子节点替换掉指定节点即可)
* 3.1 指定节点为父节点的右子节点,父节点即是前驱节点
* 3.2 指定节点为父节点的左子节点,一直向上寻找,直到找到一个父节点是爷爷节点的右孩子
*
* @param x x
* @return node 或者 null
*/
private Node<K, V> precursor(Node<K, V> x) {
if (x == null) {
return null;
// 指定节点存在左子节点
} else if (x.left != null) {
Node<K, V> l = x.left;
// 左子节点的右子节点不为空的情况,一直循环下去,直到找到前驱节点
while (l.right != null) {
l = l.right;
}
return l;
} else {
// 定义变量保存父节点和子节点
Node<K, V> parent = x.parent;
Node<K, V> child = x;
// 父节点为空的情况,即没有前驱节点,返回null, 或者还回前驱节点
while (parent!= null && child == parent.left) {
child = parent;
parent = parent.parent;
}
return parent;
}
}
/**
* 后继节点指大于当前节点的最小值
* 寻找大于当前节点的最小值有三条路:
* 1,左子节点(只能一直减小,不行)
* 2,右子节点(找到当前节点的右子节点,然后一直向左寻找)
* 3,父节点:(这种在红黑树删除用不到, 这种要么自己本身就是叶子节点直接删除,或者有一个子节点,直接子节点替换掉指定节点即可)
* 3.1,指定节点为父节点的左子节点,父节点即是后继节点
* 3.2,指定节点为父节点的右子节点,一直向上寻找,直到找到一个父节点是爷爷节点的左孩子
*
* @param x x
* @return node 或 null
*/
private Node<K, V> successor(Node<K, V> x) {
if (x == null) {
return null;
// 指定节点的右子节点
} else if (x.right != null) {
Node<K, V> r = x.right;
// 右子节点的左子节点不为空的情况,一直循环下去,直到找到后继节点
while (r.left != null) {
r = r.left;
}
return r;
} else {
// 定义变量保存父节点和子节点
Node<K, V> parent = x.parent;
Node<K, V> child = x;
// 父节点为空的情况,即没有前驱节点,返回null, 或者还回前驱节点
while (parent != null && child == parent.right) {
child = parent;
parent = parent.parent;
}
return parent;
}
}
/**
* 根据key值获取指定节点
* @param key key
* @return Node 或者 null
*/
private Node<K, V> getNode(K key) {
if (key == null) {
return null;
}
// 获取root节点,从root开始查找
Node<K, V> p = this.root;
// 定义比较变量,存储k与p节点比较的值
int cmp;
while (p != null){
// 比较key与p的大小
cmp = key.compareTo(p.key);
// key比p节点大
if (cmp > 0) {
// p节点的右子节点赋值给p,继续往右查找
p = p.right;
// key比p节点小
} else if (cmp < 0) {
// p节点的左子节点赋值给p,继续往左查找
p =p.left;
} else {
// cmp = 0,找到key就是p节点
return p;
}
}
return null;
}
/**
* 新加入节点情况统计:
* 1.插入的节点是第一个节点(根节点) : x 直接变黑
* 2.插入的节点存在父节点和叔叔节点并且叔叔节点为红色 :
* 无论x是为左节点还是右节点情况都一样处理,父节点为黑色不做处理,
* 父节点为红色,父节点和叔叔节点变成黑色,爷爷节点变成红色,爷爷节点赋值给x进行递归
* pp
* / \
* p u
* /
* x
* 3.插入的节点不存在叔叔节点:
* (1) pp (2) pp (3) pp (4) pp
* / / \ \
* p p p p
* / \ / \
* x x x x
* 仅有上述四种情况1,2 与3,4只是父节点在左与右的区别 , 而2可以通过左旋变成1 ,同理3可以通过右旋变成4
* @param x 需要平衡的节点
*/
private void fixAfterInsertion(Node<K, V> x) {
// 新存入的值,直接赋值为红色
x.color = RED;
// x不为根节点(根节点直接赋值为黑色即可), x父节点为黑色代表红黑树此时是平衡的(红色节点不影响红黑树的黑色平衡)
while (x != null && x != root && x.parent.color == RED) {
// x父节点为爷爷节点的左孩子
if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
// 爷爷节点的右孩子,就是叔叔节点
Node<K, V> u = rightOf(parentOf(parentOf(x)));
// 叔叔节点为红色,因为该方法在节点为空也为黑色,即这里为true,叔叔节点肯定存在
if (colorOf(u) == RED) {
setColor(u, BLACK);
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
x = parentOf(parentOf(x));
} else {
// x节点是父节点的右孩子,需要左旋换到一致
if (x == rightOf(parentOf(x))) {
// 将父节点赋值给x,以x左旋之后,x再次成为了左孩子
x = parentOf(x);
leftRotate(x);
}
// 父节点变成黑色
setColor(parentOf(x), BLACK);
// 爷爷节点变成红色
setColor(parentOf(parentOf(x)), RED);
// 爷爷节点右旋(对应第三种情况的第一个小情况), 旋转完成之后,父节黑色节点替代爷爷节点,因为爷爷节点以前也是黑色,
// 不会对上面节点造成影响
rightRotate(parentOf(parentOf(x)));
}
} else {
// 爷爷节点的左孩子,就是叔叔节点
Node<K, V> u = leftOf(parentOf(parentOf(x)));
// 叔叔节点为红色,因为该方法在节点为空也为黑色,即这里为true,叔叔节点肯定存在
if (colorOf(u) == RED) {
setColor(parentOf(x), BLACK);
setColor(u, BLACK);
setColor(parentOf(parentOf(x)), RED);
x = parentOf(parentOf(x));
} else {
// x节点是父节点的左孩子,需要右旋换到一致
if (x == leftOf(parentOf(x))) {
x = parentOf(x);
rightRotate(x);
}
// 父节点变成黑色
setColor(parentOf(x), BLACK);
// 爷爷节点变成红色
setColor(parentOf(parentOf(x)), RED);
// 爷爷节点左旋(对应第三种情况的第四个小情况), 旋转完成之后,父节黑色点替代爷爷节点,因为爷爷节点以前也是黑色,
// 不会对上面节点造成影响
leftRotate(parentOf(parentOf(x)));
}
}
}
root.color = BLACK;
}
/**
* 根据deleteNode的转换,将要删除的节点都转换到叶子节点或只存在一个子节点的情况(删除的节点为黑色才会进入这个方法,
* 子节点为红色直接设置为黑色,子节点为黑色即可当成即将要删除的黑色叶子平衡红色树)
*
* 1.
*
* @param x x
*/
private void fixAfterDelete(Node<K, V> x) {
while (x != root && x.color == BLACK) {
if (x == leftOf(parentOf(x))) {
Node<K, V> rNode = rightOf(parentOf(x));
// 兄弟节点为红色,不是真正的兄弟节点,x节点是父节点的左节点,通过父节点左旋,找到兄弟节点, 父节点要变成红色,右节点变成黑色(相当于234树的3节点互换位置)
if (rNode.color == RED) {
setColor(parentOf(x), RED);
setColor(rNode, BLACK);
leftRotate(parentOf(x));
rNode = rightOf(parentOf(x));
}
// 等价于(rNode != null) && (rNode.left == null) && (rNode.right == null) ,目的事判断兄弟节点的左右孩子是否存在
// 不存在代表兄弟节点也不能借出子节点,需要兄弟节点也变成红色,继续下一轮循环(下一轮循环会判断父节点颜色,为红色则直接染黑,为黑色则继续循环)
if (colorOf(leftOf(rNode)) == BLACK &&
colorOf(rightOf(rNode))) {
// 将兄弟节点变成红色,让父节点继续往上平衡,若父节点为红色,则直接变成黑色;若为黑色,继续循环
setColor(rNode, RED);
// 父节点赋值给x
x = parentOf(x);
} else {
// 当兄弟节点的右子节点为空时,代表左子节点必定不为空,直接左旋父节点,会造成兄弟节点没有右孩子,直接把子节点给x节点,会影响大小排序,
// 所以必须先右旋兄弟节点,保证其有右孩子
if (colorOf(rightOf(rNode)) == BLACK) {
// 这种情况是234树3节点的情况,兄弟节点黑色,其子节点为红色,右旋的同时,颜色需要互换
setColor(rNode, RED);
setColor(leftOf(rNode), BLACK);
rightRotate(rNode);
// 重新获取新的兄弟节点
rNode = rightOf(parentOf(x));
}
// 因为兄弟节点要左旋顶替父节点的位置,需要将父节点的颜色赋值给兄弟节点
setColor(rNode, colorOf(parentOf(x)));
// 因为父节点需要顶替x节点的位置,需要将父节点染黑
setColor(parentOf(x), BLACK);
// 兄弟节点的右子节点需要顶替兄弟节点的位置,需要将其染黑
setColor(rightOf(rNode), BLACK);
// 左旋
leftRotate(parentOf(x));
// 跳出循环
x = root;
}
} else {
Node<K, V> lNode = leftOf(parentOf(x));
// 兄弟节点为红色,不是真正的兄弟节点,x节点是父节点的右节点,通过父节点右旋,找到兄弟节点, 父节点要变成红色,右节点变成黑色(相当于234树的3节点互换位置)
if (lNode.color == RED) {
setColor(parentOf(x), RED);
setColor(lNode, BLACK);
rightRotate(x);
lNode = leftOf(parentOf(x));
}
// 等价于(rNode != null) && (rNode.left == null) && (rNode.right == null) ,目的事判断兄弟节点的左右孩子是否存在
// 不存在代表兄弟节点也不能借出子节点,需要兄弟节点也变成红色,继续下一轮循环(下一轮循环会判断父节点颜色,为红色则直接染黑,为黑色则继续循环)
if (colorOf(leftOf(lNode)) == BLACK &&
colorOf(rightOf(lNode))) {
// 将兄弟节点变成红色,让父节点继续往上平衡,若父节点为红色,则直接变成黑色;若为黑色,继续循环
setColor(lNode, RED);
// 父节点赋值给x
x = parentOf(x);
} else {
// 当兄弟节点的左子节点为空时,代表右子节点必定不为空,直接右旋父节点,会造成兄弟节点没有左孩子,直接把子节点给x节点,会影响大小排序,
// 所以必须先左旋兄弟节点,保证其有左孩子
if (colorOf(leftOf(lNode)) == BLACK) {
// 这种情况是234树3节点的情况,兄弟节点黑色,其子节点为红色,右旋的同时,颜色需要互换
setColor(lNode, RED);
setColor(rightOf(lNode), BLACK);
leftRotate(lNode);
// 重新获取新的兄弟节点
lNode = leftOf(parentOf(x));
}
// 因为兄弟节点要右旋顶替父节点的位置,需要将父节点的颜色赋值给兄弟节点
setColor(lNode, colorOf(parentOf(x)));
// 因为父节点需要顶替x节点的位置,需要将父节点染黑
setColor(parentOf(x), BLACK);
// 兄弟节点的左子节点需要顶替兄弟节点的位置,需要将其染黑
setColor(leftOf(lNode), BLACK);
// 右旋
rightRotate(parentOf(x));
// 跳出循环
x = root;
}
}
}
// x节点为红色,直接染黑
setColor(x, BLACK);
}
/**
* 左旋概念: 将指定x节点左旋,是以x节点的右孩子为轴心,x节点向左旋转,(①)x成为其右孩子的左孩子,其右孩子成为x节点的父节点,
* (②)x节点的原始父节点成为其原始右孩子的父节点, (③)x节点的原始右孩子的左孩子(原始孙子节点)成为x节点的右孩子,其余节点保持不变
* 左旋图形
* p p
* / /
* x xr
* / \ / \
* xl xr ==> x rr
* / \ / \
* rl rr xl rl
*
* @param x 需要左旋的节点
*/
private void leftRotate(Node<K, V> x) {
Node<K, V> xr;
if (x != null && (xr = x.right) != null) {
// x节点的右子节点的左节点,赋值给x节点的右子节点(x的孙子成了儿子) (③)
Node<K, V> rl = x.right = xr.left;
if (rl != null) {
// 孙子节点若存在,改变孙子节点的父节点(rl的爷爷成了爸爸)
rl.parent = x;
}
// x节点的父节点赋值给右子节点的父节点(xr的爷爷成了爸爸) (②)
Node<K, V> xp = xr.parent = x.parent;
if (xp == null) {
// x不存在父节点,即x就是根节点,故让xr成为新根节点
this.root = xr;
} else if (x == leftOf(xp)) {
// xr替代x成为爷爷节点的左子节点
xp.left = xr;
} else {
// xr替代x成为爷爷节点的右子节点
xp.right = xr;
}
// x成为其右孩子的左孩子 (①)
xr.left = x;
// 其右孩子成为x节点的父节点
x.parent = xr;
}
}
/**
* 右旋概念: 将指定x节点左旋,是以x节点的左孩子为轴心,x节点向右旋转,(①)x成为其左孩子的右孩子,其左孩子成为x节点的父节点,
* (②)x节点的原始父节点成为其原始左孩子的父节点, (③)x节点的原始左孩子的右孩子(原始孙子节点)成为x节点的左孩子,其余节点保持不变
* 右旋图形
* x xl
* / \ / \
* xl xr ==> ll x
* / \ / \
* ll lr lr xr
* @param x 需要旋转的节点
*/
private void rightRotate(Node<K, V> x) {
Node<K, V> xl;
if (x != null && (xl = x.left) != null) {
// x节点的左孩子的右孩子,成为x节点的左孩子(x的孙子成为儿子) (③)
Node<K, V> lr = x.left = xl.right;
if (lr != null) {
// 孙子节点若存在,改变孙子节点的父节点(lr的爷爷成了爸爸)
lr.parent = x;
}
// x节点的父节点赋值给左子节点的父节点(xl的爷爷成了爸爸) (②)
Node<K, V> xp = xl.parent = x.parent;
if (xp == null) {
// x不存在父节点,即x就是根节点,故让xl成为新根节点
this.root = xl;
} else if (x == rightOf(xp)){
// xl替代x成为爷爷节点的右子节点
xp.right = xl;
} else {
// xl替代x成为爷爷节点的左子节点
xp.left = xl;
}
// x成为其左孩子的右孩子 (①)
xl.right = x;
// 其左孩子成为x节点的父节点
x.parent = xl;
}
}
public Node<K, V> getRoot() {
return root;
}
static final class Node<K, V> {
K key;
V value;
Node<K, V> left;
Node<K, V> right;
Node<K, V> parent;
boolean color = BLACK;
public Node(K key, V value, Node<K, V> parent) {
this.key = key;
this.value = value;
this.parent = parent;
}
public K getKey() {
return key;
}
public void setKey(K key) {
this.key = key;
}
public V getValue() {
return value;
}
public void setValue(V value) {
this.value = value;
}
public Node<K, V> getLeft() {
return left;
}
public void setLeft(Node<K, V> left) {
this.left = left;
}
public Node<K, V> getRight() {
return right;
}
public void setRight(Node<K, V> right) {
this.right = right;
}
public Node<K, V> getParent() {
return parent;
}
public void setParent(Node<K, V> parent) {
this.parent = parent;
}
public boolean isColor() {
return color;
}
public void setColor(boolean color) {
this.color = color;
}
}
}