Java1.8 HashMap 红黑树
0.红黑树的意义
相比于平衡二叉树,在java1.8中HashMap选用了红黑树作为链表过长时提高效率的方法.
因为平衡二叉树为了保持平衡所付出的代价过大,平衡二叉树的查找效率很高但是插入和删除后为了保持平衡所需要的旋转次数比红黑树多,所以选用非严格平衡的红黑树来降低插入删除后的旋转次数
1. 红黑树的定义(一定要慢慢认真读)
-
1、每个结点或是红色的,或是黑色的
-
2、根节点是黑色的
-
3、每个叶结点(NIL)是黑色的
-
4、如果一个节点是红色的,则它的两个儿子都是黑色的。不能有两个红色节点相连
-
5、对于每个结点,从该结点到其叶子结点构成的所有路径上的黑结点个数相同。俗称:黑节点高度.
-
性质的推论:如果存在一个黑节点,那么这个节点必定会有两个子节点
看下图
从根节点开始,每条支路上的黑色节点个数都是三,所以黑高为三
红黑树不是一个二叉查找树,从图中可以看到的是,根节点0010的右子树比左子树高,但是左子树和右子树的黑结点层数是一样的,也就符合了性质5.红黑树的这种平衡被称为黑色完美平衡
2. 红黑树的自平衡
红黑树的自平衡依靠:左旋,右旋,和变色
- 变色:节点的颜色从当前颜色变成另一个颜色
- 右旋(LL): 以某个节点为旋转节点,其左子节点变为旋转节点的父节点,左子节点的右子节点变为旋转节点的左子节点,右子节点保持不变
- 左旋(RR): 以某个节点为旋转节点,其右子节点变为旋转节点的父节点,右子节点的左子节点变为旋转节点的右子节点,左子节点保持不变
左旋图例:
右旋图例:
3.红黑树的插入
插入工作主要需要两个工作.一 找到应该插入的位置 ,二 插入元素之后对红黑树自平衡操作
查询插入的位置,红黑树的插入方法和平衡二叉树一样.
插入后自平衡
简单情况
情景1:红黑树为空树,将节点染黑
情景2:插入节点的key已经存在,替换value即可
情景3:插入节点父节点为黑结点,因为所插入的路径,黑色节点没有变化,所以红黑树依旧平衡
需要讨论情况
- 情景4:插入节点的父节点为红色
-
4.1 叔叔节点存在,并且为红色(叔父双红) ,将爸爸叔叔染色为黑色,将爷爷染色为红色,并且再以爷爷节点为当前节点,进行下一轮处理
本图转自B站暴躁的小刘老师
-
4.2 叔叔节点不存在,或者为黑色.父节点为爷爷节点的左子树
-
4.2.1 插入节点为其父节点的左子节点(LL),将爸爸染色为黑色,将爷爷染色为红色,然后以爷爷节点右旋就完成了
-
4.2.2 插入节点为其父节点的右子节点(LR),将爸爸节点进行一次左旋,得到LL双红后LL处理
-
-
4.3 叔叔节点不存在,或者为黑色,父节点为爷爷节点的右子树
-
4.3.1 插入节点为其父节点的右子节点(RR情况),将爸爸染色为黑色,将爷爷染色为红色,然后以爷爷节点左旋
-
4.3.2 插入节点为其父节点的左子节点(RL情况),将爸爸节点右旋然后RR处理
-
-
4.喜闻乐见的代码
public class RBTree<K extends Comparable<K>, V> {
private static final boolean RED = true;
private static final boolean BLEAK = false;
private RBNode root;
public RBNode getRoot() {
return root;
}
/**
* 返回节点的父节点
*
* @param node
* @return
*/
private RBNode parentOf(RBNode node) {
if (node != null) {
return node.parent;
}
return null;
}
/**
* 判断是否为红节点
*
* @param node
* @return
*/
private boolean isRed(RBNode node) {
if (node != null)
return node.color == RED;
return false;
}
/**
* 设置节点为红色
*
* @param node
*/
private void setRed(RBNode node) {
if (node != null) {
node.color = RED;
}
}
/**
* 判断节点是否为红色
*
* @param node
* @return
*/
private boolean isBlack(RBNode node) {
if (node != null)
return node.color == BLEAK;
return false;
}
/**
* 设置节点为黑色
*
* @param node
*/
private void setBlack(RBNode node) {
if (node != null) {
node.color = BLEAK;
}
}
/**
* 中序打印
*/
public void inOrderPrint() {
inOrderPrint(this.root);
}
private void inOrderPrint(RBNode root) {
if (root != null) {
inOrderPrint(root.left);
System.out.println("Key" + root.key + "Value:" + root.value);
inOrderPrint(root.right);
}
}
/**
* 左旋
* <p>
* P P
* | |
* X ----------> Y
* / \ / \
* lx Y x ry
* / \ / \
* ly ry lx ly
*/
private void leftRotate(RBNode x) {
RBNode y = x.right;
//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;
}
//3.将x的父节点更新为y,将y的左子节点更新为x
x.parent = y;
y.left = x;
}
/**
* 右旋
* <p>
* P P
* | |
* Y X
* / \ --------> / \
* x ry lx Y
* / \ / \
* lx ly ly ry
*/
private void rightRotate(RBNode y) {
RBNode x = y.left;
y.left = x.right;
if (x.right != null) {
x.right.parent = y;
}
if (y.parent != null) {
x.parent = y.parent;
//判断原节点在他根节点的位置
if (y == y.parent.right) {
y.parent.right = y;
} else {
y.parent.left = y;
}
} else {
this.root = x;
}
//3.将y的父节点更新为x,将y的左子节点更新为x
y.parent = x;
x.right = y;
}
/**
* 插入方法
*
* @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) {
//第一步:查找查找当前父节点
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) {
parent.right = node;
} else {//当前node的key比parent的key小,需要把node放入parent的左子节点
parent.left = node;
}
} else {
this.root = node;
}
RBTreeFixed(node);
}
/**
* 插入后平衡红黑树的方法
* 情景1:红黑树为空树,将节点染黑
* 情景2:插入节点的key已经存在,不需要处理
* 情景3:插入节点父节点为黑结点,因为所插入的路径,黑色节点没有变化,所以红黑树依旧平衡
* <p>
* 情景4:插入节点的父节点为红色
* 4.1 叔叔节点存在,并且为红色(叔父双红) ,将爸爸叔叔染色为黑色,将爷爷染色为红色,并且再以爷爷节点为当前节点,进行下一轮处理
* 4.2 叔叔节点不存在,或者为黑色.父节点为爷爷节点的左子树
* 4.2.1 插入节点为其父节点的左子节点(LL),将爸爸染色为黑色,将爷爷染色为红色,然后以爷爷节点右旋就完成了
* 4.2.2 插入节点为其父节点的右子节点(LR),将爸爸节点进行一次左旋,得到LL双红后LL处理
* 4.3 叔叔节点不存在,或者为黑色,父节点为爷爷节点的右子树
* 4.3.1 插入节点为其父节点的右子节点(RR情况),将爸爸染色为黑色,将爷爷染色为红色,然后以爷爷节点左旋
* 4.3.2 插入节点为其父节点的左子节点(RL情况),将爸爸节点右旋然后RR处理
*/
private void RBTreeFixed(RBNode node) {
this.root.setColor(BLEAK);
RBNode parent = parentOf(node);
RBNode gparent = parentOf(parent);
//情景四
if (isRed(parent) && parent != null) {
RBNode uncle = null;
if (parent == gparent.left){
uncle = gparent.right;
//情景4.1 叔叔节点存在,并且为红色 (父-叔 双红)
if (uncle != null && isRed(uncle)){
//将爸爸和叔叔染色为黑色,将爷爷染色为红色,并且再以爷爷节点为当前节点进行下一轮处理
setBlack(parent);
setBlack(uncle);
setRed(gparent);
RBTreeFixed(gparent);
return;
}
//4.2 :叔叔节点不存在,或者为黑色
if(isBlack(uncle) || uncle == null){
if (node == parent.left){
rightRotate(gparent);
setBlack(parent);
setRed(gparent);
return;
}else{
leftRotate(parent);
RBTreeFixed(parent);
return;
}
}
}else{//父节点为爷爷节点的右子节点
uncle = gparent.right;
//情景4.1 叔叔节点存在,并且为红色 (父-叔 双红)
if (uncle != null && isRed(uncle)){
//将爸爸和叔叔染色为黑色,将爷爷染色为红色,并且再以爷爷节点为当前节点进行下一轮处理
setBlack(parent);
setBlack(uncle);
setRed(gparent);
RBTreeFixed(gparent);
return;
}
//4.3 :叔叔节点不存在,或者为黑色
if(isBlack(uncle) || uncle == null){
if (node == parent.right){
leftRotate(gparent);
setBlack(parent);
setRed(gparent);
return;
}else{
rightRotate(parent);
RBTreeFixed(parent);
return;
}
}
}
}
}
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(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() {
}
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 getColor() {
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;
}
}
}
个人小结:大三了第一次写博客,想对一直学的东西进行复习,加深了对红黑树的部分理解,删除部分暂时还没有去研究,之后有时间会补充
思考题:在网上找的思考题
1.黑结点可以同时包含一个红子结点和一个黑子结点吗?
答:可以
2.请画出下图添加后自平衡处理过程
答: