原理
- 每个节点要么是红色要么是黑色
- 根节点是黑色
- 每个叶子节点(NIL)是黑色
- 每个红色节点的两个子节点一定都是黑色,不能红红相连
- 任意一个节点到每个叶子节点的路径都包含数量相同的黑节点(黑高)
自平衡操作
-
变色,节点的颜色由红色变黑或者由黑变红
-
左旋:以某个结点作为支点(旋转结点),其右子结点变为旋转结点的父结点,右子结点的左子结点变为旋转结点的右子结点,左子结点保持不变。
-
右旋:以某个结点作为支点(旋转结点),其左子结点变为旋转结点的父结点,左子结点的右子结点变为旋转结点的左子结点,右子结点保持不变
插入操作
- 插入节点,必须为红色,理由很简单,红色在父节点(如果存在)为黑色节点时,红黑树的黑色平衡没被破坏,不需要做自平衡操作。
但如果插入结点是黑色,那么插入位置所在的子树黑色结点总是多1,必须做自平衡。
1. 情景1:红黑树为空树,直接把插入结点作为根结点就行
注意:根据红黑树性质2:根节点是黑色。还需要把插入结点设为黑色。
2. 情景2:插入结点的Key已存在处理:更新当前节点的值,为插入节点的值
3. 情景3:插入结点的父结点为黑结点由于插入的结点是红色的,当插入结点是红色时,并不会影响红黑树的平衡,直接插入即可,无需做自平衡。
4. 情景4 :插入节点的父节点为红色,根据性质2插入节点的父节点为红色,那么肯定存在爷爷节点
4.1 . 叔叔节点存在并且为红色,由性质4可知,红色节点不能相连,那么爷爷节点肯定为黑色节点。因为不可以同时存在两个相连的红节点,那么此时插入子树的红黑层数的情况是:黑红红。那么将其改为红黑红即可。
- 将P和U节点改为黑色
- 将PP改为红色
- 将PP设置为当前节点,进行后续处理
4.2. 叔叔结点不存在或为黑结点,并且插入结点的父亲结点是祖父结点的左子结点
注意:单纯从插入前来看,叔叔节点非红即空(NIL节点),否则的话破坏了红黑树性质5,此路径会比其它路径多一个黑色节点
4.2.1. 新插入节点,为其父节点的左子节点(LL红色情况)
- 处理:
- 变颜色:将P设置为黑色,将PP设置为红色
- 对PP节点进行右旋
4.2.2. 新插入节点,为其父节点的右子节点(LR红色情况)
处理:
1.对P进行左旋
2. 将P设置为当前节点,得到LL红色情况
3. 按照LL红色情况处理(1.变颜色 2.右旋PP)
4.3. 叔叔结点不存在或为黑结点,并且插入结点的父亲结点是祖父结点的右子结点
4.3.1 新插入节点,为其父节点的右子节点(RR红色情况)
- 处理:
- 变颜色:将P设置为黑色,将PP设置为红色
- 对PP节点进行左旋
4.3.2 新插入节点,为其父节点的左子节点(RL红色情况)
- 处理:
- 对P进行右旋
- 将P设置为当前节点,得到RR红色情况
- 按照RR红色情况处理(1.变颜色 2.左旋PP)
代码
package cn.wy.datastruct.tree;
import java.util.Scanner;
public class RBTree <K extends Comparable<K>, V> {
//定义颜色常量
private static final boolean RED = true;
private static final boolean BLACK = false;
//红黑树的树根
private static RBNode root;
public RBNode getRoot() {
return root;
}
/**
* 公开的插入接口
* @param key 键
* @param value 值
*/
public void insert(K key, V value) {
RBNode node = new RBNode();
node.setKey(key);
node.setValue(value);
node.setColor(RED);
insert(node);
}
/**
* 内部插入接口定义
*/
private void insert(RBNode node) {
//1.找到插入的位置
RBNode parent = null;
RBNode x = this.root;
while(x != null) {
parent = x;
//a > b 则返回 1,否则返回 -1 ,相等返回0
int cmp = node.key.compareTo(parent.key);
if(cmp < 0) {
x = x.left;
} else if(cmp == 0) {
parent.setValue(node.value);
return;
} else {
x = x.right;
}
}
node.parent = parent;
if(parent != null) {
if(node.key.compareTo(parent.key) < 0) {
parent.left = node;
} else {
parent.right = node;
}
} else {
this.root = node;
}
//插入之后需要进行修复红黑树,让红黑树再次平衡。
insertFixUp(node);
}
/**
* 插入后修复红黑树平衡的方法
* |---情景1:红黑树为空树
* |---情景2:插入节点的key已经存在
* |---情景3:插入节点的父节点为黑色
*
* 情景4 需要咱们去处理
* |---情景4:插入节点的父节点为红色
* |---情景4.1:叔叔节点存在,并且为红色(父-叔 双红)
* |---情景4.2:叔叔节点不存在,或者为黑色,父节点为爷爷节点的左子树
* |---情景4.2.1:插入节点为其父节点的左子节点(LL情况)
* |---情景4.2.2:插入节点为其父节点的右子节点(LR情况)
* |---情景4.3:叔叔节点不存在,或者为黑色,父节点为爷爷节点的右子树
* |---情景4.3.1:插入节点为其父节点的右子节点(RR情况)
* |---情景4.3.2:插入节点为其父节点的左子节点(RL情况)
*/
private void insertFixUp(RBNode node) {
RBNode parent = parentOf(node);
RBNode gparent = parentOf(parent);
//存在父节点且父节点为红色
if(parent != null && isRed(parent