红黑树
二叉搜索树
删除节点:
- 该节点是叶子节点
- 该节点有一个子节点
- 该节点有两个子节点
红黑树和平衡二叉搜索树
红黑树和平衡二叉搜索树有一定区别
二叉搜索树是强平衡二叉树
特点: 1) 左右子树的高度差不超过1
2) 左右两子树也是一颗平衡二叉树
红黑树是弱平衡二叉树
红黑树性质:
- 每一个节点要么是黑色要么是红色(颜色二选一)
- 根节点一定是黑色
- 叶子节点一定是黑色
- 红色节点的子节点一定是黑色(不可能有两个红色节点相连)
- 任意一节点到它下面的叶子节点的路径都包含相同数量的黑节点(俗称:黑高)
- 5.1:如果已知一个节点存在了黑色子节点,那么该节点肯定有两个子节点
红黑树虽然不是完美平衡二叉搜索树,但是左右子树的黑色节点的层数是相同的,黑高是相同的(性质5),也即任意一个节点到叶子节点的路径都包含数量相同的黑色节点。
所以我们叫红黑树 的这种平衡为 黑色完美平衡
平衡二叉搜索树能自平衡是有 左左(右旋一次),右右(左旋一次),左右(先左旋,再右旋),右左(先右旋,在左旋)
红黑树
红黑树自平衡有三种操作:左旋,右旋和变色
红黑树插入:
新建插入节点一定是红色(因为红色相比于黑色更不容易破环红黑树的结构)
- 红黑树为空树
- 此时第一个节点为根节点,要为黑色 - 插入元素已经存在
- 则替代它(颜色也要和以前一样) - 插入节点的父节点为黑节点
- 由于插入的节点为红色,不会影响红黑树的平衡,不需要自平衡
- 插入节点的父节点为红色
可以推出父节点不是根节点,则插入节点肯定有爷爷,并且爷爷为黑色
4.1. 叔叔节点存在并且为红色节点
- 把爸爸和叔叔变成黑色,把爷爷变成红色
-
4.2 :叔叔节点不存在或为黑色节点,并且插入节点的父亲是爷爷节点的左子节点
4.2.1:当插入节点 I 为P节点的左边
一个右旋
4.2.2:当插入节点 I 为P节点的右边
先左旋,
自己 I 变黑,爷爷PP变红
再右旋
4.2 :叔叔节点不存在或为黑色节点,并且插入节点的父亲是爷爷节点的右子节点
4.3.1:当插入节点 I 为P节点的右边(RR红色情况)
PP变红,P变黑,I变黑
以P节点左旋
4.3.2:当插入节点 I 为P节点的左边(RL红色情况)
对P右旋,PP,P变色,以PP为节点左旋
练习:
代码实现红黑树
public class RBTree<K extends Comparable<K>,V>{
private static final boolean RED=true;
private static final boolean BLACK=false;
/**
* 树根的引用
*/
private RBNode root;
public RBNode getRoot() {
return root;
}
public void setRoot(RBNode root) {
this.root = root;
}
/**
* 中序打印二叉树
*/
public void inOrderPrint() {
inOrderPrint(root);
}
private void inOrderPrint(RBNode node) {
if(node!=null) {
inOrderPrint(node.left);
System.out.println("key:"+node.key+",value:"+node.value);
inOrderPrint(node.right);
}
}
/**
* 设置节点为红色
*/
private void setRed(RBNode node) {
if(node!=null) {
node.color=RED;
}
}
/**
* 设置节点为黑色
*/
private void setBlack(RBNode node) {
if(node!=null) {
node.color=BLACK;
}
}
/**
* 节点是否为红色
*/
private boolean isRed(RBNode node) {
if(node!=null) {
return node.color == RED;
}
return false;
}
/**
* 节点是否为黑色
*/
private boolean isBlack(RBNode node) {
if(node!=null) {
return node.color == BLACK;
}
return false;
}
/**
* 获取当前节点的父节点
*/
private RBNode parentOf(RBNode node) {
if(node!=null) {
return node.parent;
}
return null;
}
/**
* 公开的输入方法
*/
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) {
//第一步:查找当前node的父节点
RBNode parent = null;
RBNode x = this.root;
while(x != null) {
parent = x;
//cmp > 0 说明node.key 大于 x.key 需要到x的右子树查找
//cmp == 0 说明node.key 等于x。key 说明需要进行替换操作
//cmp < 0 说明node.key 小于x.key 需要到x的左子树查找
int cmp=node.key.compareTo(x.key);
if(cmp>0) {
x=x.right;
}else if(cmp==0) {
x.setValue(node.getValue());
return;
}else {
x=x.left;
}
}
node.parent = parent;
if(parent!=null) {
//判断node与parent的key谁大
int cmp=node.key.compareTo(parent.key);
if(cmp>0) {//当前node的key比parent的key大,需要把node放入parent的右子节点
parent.right = node;
}else {//当前node的key比parent的key小,需要把node放入parent的左子节点
parent.left=node;
}
}else {
this.root=node;
}
//需要调用修复红黑树平衡的方法
insertFixup(node);
}
/**
* 插入后修复红黑树平衡的方法
* |---情景1:如果红黑树为空树,需要将根节点染为黑色
* |---情景2:如果插入节点的key已经存在,(这种情况不需要处理,因为修改树中的值不会触发红黑树修复平衡方法)
* |---情景3:如果插入节点的父节点为黑色,这种情况不需要处理,(参考红黑树的性质4和性指5去理解)
* (因为所插入的路径中,黑色节点数没发生变化,所以红黑树依然平衡)
*
* 情景4 需要去处理的情景
* |---情景4:插入节点的父节点为红色,(违反红黑树性质4,不能有两个红色节点相连)
* |---情景4.1:叔叔节点存在,并且为红色(父-叔 双红)
* 处理:将爸爸和叔叔染成黑色,将爷爷染成红色,并且再以爷爷节点为当前节点,进行下一轮处理
*
* |---情景4.2:叔叔节点不存在,或者为黑色,父节点为爷爷节点的左子树
* 处理:
* |---情景4.2.1:插入节点为其父节点的左子节点(LL情况)
* 处理:将父节点染为黑色,将爷爷染为红色,然后以爷爷节点右旋即可
* |---情景4.2.2:插入节点为其父节点的右子节点(LR情况)
* 处理:将父节点进行一次左旋,得到LL双红情景(4.2.1),然后指定父节点为当前节点进行下一轮处理
*
* |---情景4.3:叔叔节点不存在,或者为黑色,父节点为爷爷节点的右子树
* |---情景4.3.1:插入节点为其父节点的右子节点(RR情况)
* 处理:将父节点染为黑色,将爷爷节点染为红色,然后以爷爷节点左旋即可
* |---情景4.3.2:插入节点为其父节点的左子节点(RL情况)
* 处理:以父节点进行一次右旋,得到RR双红情景(4.3.1),然后指定父节点为当前节点进行下一轮处理
*/
private void insertFixup(RBNode node) {
this.root.setColor(BLACK);
RBNode parent = parentOf(node);
RBNode gparent = parentOf(parent);
//情景4:插入节点的父节点为红色
if(parent!=null && isRed(parent)) {
//如果父节点是红色,那么一定存在爷爷节点,因为根节点不可能是红色
RBNode uncle=null;
//父节点为爷爷节点的左子树
if(parent==gparent.left) {
uncle = gparent.right;
//情景4.1:叔叔存在,并且为红色(父-叔 双红)
if(uncle != null && isRed(uncle)) {
//将爸爸和叔叔染色为黑色,将爷爷染色为红色,并且再以爷爷节点为当前节点 进行下一轮处理
setBlack(parent);
setBlack(uncle);
setBlack(gparent);
insertFixup(gparent);
return;
}
//情景4.2:叔叔不存在,或者为黑色,父节点为爷爷节点的左子树
if(uncle == null || isBlack(uncle)) {
//情景4.2.1:插入节点为其父节点的左子节点(LL情况)
//将父节点染为黑色,将爷爷染为红色,然后以爷爷节点右旋即可
if(node == parent.left) {
setBlack(parent);
setRed(gparent);
rightRotate(gparent);
return ;
}
//情景4.2.2:插入节点为其父节点的右子节点(LR情况)
//将父节点进行一次左旋,得到LL双红情景(4.2.1),然后指定父节点为当前节点进行下一轮处理
if(node == parent.right){
leftRotate(parent);
insertFixup(parent);
return ;
}
}
}else {//父节点为爷爷节点的右子树
uncle=gparent.left;
//情景4.1:叔叔存在,并且为红色(父-叔 双红)
if(uncle != null && isRed(uncle)) {
//将爸爸和叔叔染色为黑色,将爷爷染色为红色,并且再以爷爷节点为当前节点 进行下一轮处理
setBlack(parent);
setBlack(uncle);
setBlack(gparent);
insertFixup(gparent);
return;
}
//情景4.3:叔叔节点不存在,或者为黑色,父节点为爷爷节点的右子树
if(uncle!=null || isBlack(uncle)) {
//情景4.3.1:插入节点为其父节点的右子节点(RR情况)
//将父节点染为黑色,将爷爷节点染为红色,然后以爷爷节点左旋即可
if(node == parent.right) {
setBlack(parent);
setRed(gparent);
leftRotate(gparent);
return ;
}
//情景4.3.2:插入节点为其父节点的左子节点(RL情况)
//处理:以父节点进行一次右旋,得到RR双红情景(4.3.1),然后指定父节点为当前节点进行下一轮处理
if(node == parent.left) {
rightRotate(parent);
insertFixup(parent);
return;
}
}
}
}
}
/**
* 左旋方法
* 左旋示意图:左旋x节点
* p p
* | |
* x y
* / \ ----> / \
* lx y x ry
* / \ / \
* ly ry lx ly
*
* 左旋做了几件事?
* 1.将x的右子节点指向y的左子节点(ly),并且把y的左子节点更新为x
* 2.当x的父节点(不为空时),更新y的父节点为x的父节点,并将x的父节点 指定 子树(当前x的子树位置) 指定为y
* 3.将x的父节点更新为y,将y的左子节点更新为x
*/
private void leftRotate(RBNode x) {
RBNode y=x.left;
//1.将x的右子节点直接向y的左子节点(ly),将y的左子节点更新为x
x.right=y.left;
if(y.left != null) {
y.left.parent=x;
}
//2.当x的父节点(不为空时),更新y的父节点为x的父节点,并将x的父节点 指定 子树(当前x的子树位置)指定为y
if(x.parent!=null) {
y.parent=x.parent;
if(x==x.parent.left) {
x.parent.left=y;
}else {
x.parent.right=y;
}
}else {//说明x是根节点,此时需要更新y为根节点引用
this.root=y;
this.root.parent = null;
}
//3.将x的父节点更新为y,将y的左子节点更新为x
x.parent=y;
y.left=x;
}
/**
* 右旋方法
* 右旋示意图:右旋y节点
*
* p p
* | |
* y x
* / \ ----> / \
* x ry lx y
* / \ / \
* lx ly ly ry
*
* 右旋都做了几件事?
* 1.将y的左子节点指向x的右子节点,并且更新x的右子节点的父节点为y
* 2.当y的父节点不为空时,更新x的父节点为y的父节点,更新y的父节点的指定子节点(y当前位置) 为x
* 3.更新y 的父节点为x ,更新x 的右子节点为y
*/
private void rightRotate(RBNode y) {
RBNode x=y.left;
//1.将y的左子节点指向x的右子节点,并且更新x的右子节点的父节点为y
if(x.right!=null) {
y.left=x.right;
}
//当y的父节点不为空时,更新x的父节点为y的父节点,更新y的父节点的指定子节点(y当前的位置)为x;
if(y.parent!=null) {
x.parent=y.parent;
if(y.parent.left==y) {
y.parent.left=x;
}else {
y.parent.right=x;
}
}else {//说明x是根节点,此时需要更新y为根节点引用
this.root=x;
this.root.parent = null;
}
//3.更新y 的父节点为x ,更新x 的右子节点为y
y.parent=x;
x.right=y;
}
static class RBNode<K extends Comparable<K>,V>{
private RBNode parent;
private RBNode left;
private RBNode right;
private boolean color;
private K key;
private V value;
public RBNode() {
super();
}
public RBNode(RBNode parent, RBNode left, RBNode right, boolean color, K key, V value) {
this.parent = parent;
this.left = left;
this.right = right;
this.color = color;
this.key = key;
this.value = value;
}
public RBNode getParent() {
return parent;
}
public void setParent(RBNode parent) {
this.parent = parent;
}
public RBNode getLeft() {
return left;
}
public void setLeft(RBNode left) {
this.left = left;
}
public RBNode getRight() {
return right;
}
public void setRight(RBNode right) {
this.right = right;
}
public boolean isColor() {
return color;
}
public void setColor(boolean color) {
this.color = color;
}
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;
}
}
}
TreeOperation()
/**
* @Auther: csp1999
* @Date: 2020/11/09/15:10
* @Description: 打印红黑树的工具类
*/
public class TreeOperation {
/*
树的结构示例:
1
/ \
2 3
/ \ / \
4 5 6 7
*/
// 用于获得树的层数
public static int getTreeDepth(RBTree.RBNode root) {
return root == null ? 0 : (1 + Math.max(getTreeDepth(root.getLeft()), getTreeDepth(root.getRight())));
}
private static void writeArray(RBTree.RBNode currNode, int rowIndex, int columnIndex, String[][] res, int treeDepth) {
// 保证输入的树不为空
if (currNode == null) return;
// 先将当前节点保存到二维数组中
res[rowIndex][columnIndex] = String.valueOf(currNode.getKey() /*+ "-" + (currNode.isColor() ? "R" : "B") + ""*/);
// 计算当前位于树的第几层
int currLevel = ((rowIndex + 1) / 2);
// 若到了最后一层,则返回
if (currLevel == treeDepth) return;
// 计算当前行到下一行,每个元素之间的间隔(下一行的列索引与当前元素的列索引之间的间隔)
int gap = treeDepth - currLevel - 1;
// 对左儿子进行判断,若有左儿子,则记录相应的"/"与左儿子的值
if (currNode.getLeft() != null) {
res[rowIndex + 1][columnIndex - gap] = "/";
writeArray(currNode.getLeft(), rowIndex + 2, columnIndex - gap * 2, res, treeDepth);
}
// 对右儿子进行判断,若有右儿子,则记录相应的"\"与右儿子的值
if (currNode.getRight() != null) {
res[rowIndex + 1][columnIndex + gap] = "\\";
writeArray(currNode.getRight(), rowIndex + 2, columnIndex + gap * 2, res, treeDepth);
}
}
public static void show(RBTree.RBNode root) {
if (root == null) System.out.println("EMPTY!");
// 得到树的深度
int treeDepth = getTreeDepth(root);
// 最后一行的宽度为2的(n - 1)次方乘3,再加1
// 作为整个二维数组的宽度
int arrayHeight = treeDepth * 2 - 1;
int arrayWidth = (2 << (treeDepth - 2)) * 3 + 1;
// 用一个字符串数组来存储每个位置应显示的元素
String[][] res = new String[arrayHeight][arrayWidth];
// 对数组进行初始化,默认为一个空格
for (int i = 0; i < arrayHeight; i++) {
for (int j = 0; j < arrayWidth; j++) {
res[i][j] = " ";
}
}
// 从根节点开始,递归处理整个树
// res[0][(arrayWidth + 1)/ 2] = (char)(root.val + '0');
writeArray(root, 0, arrayWidth / 2, res, treeDepth);
// 此时,已经将所有需要显示的元素储存到了二维数组中,将其拼接并打印即可
for (String[] line : res) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < line.length; i++) {
sb.append(line[i]);
if (line[i].length() > 1 && i <= line.length - 1) {
i += line[i].length() > 4 ? 2 : line[i].length() - 1;
}
}
System.out.println(sb.toString());
}
}
}
测试:
public static void main(String[] args) {
RBTree<String, Object> rbtree = new RBTree();
//测试输入:ijkgefhdabc
while(true) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入key:");
String key = sc.next();
rbtree.insert(key, null);
TreeOperation.show(rbtree.getRoot());
}
}