- 博主会对算法与数据结构会不断进行更新,敬请期待,如有什么建议,欢迎联系。
- 二叉树中最特殊的红黑树可以算是其中一个,我们知道jdk8中对HashMap进行了优化,底层数据结构采用了数组+链表+红黑树,当链表达到8时,或数组达到64时就会转成红黑树。红黑树可以说是2-3查找树的升级,红黑树也是一种平衡树,平衡树解决了如果插入的数据为有序元素,例如:1,2,3,4,5,6则二叉树就会变成链表的问题。
红黑树的定义:
红黑树是含有红黑链接,并满足下列条件的二叉平衡树:
- 红链接均为左连接;
- 没有任何一个节点同时和两个红链接相连;
- 该树是完美黑色平衡树,即任意空链接到到根节点的路径黑链接数量相等;
下面是红黑树和2-3树的对应关系:
红黑树节点设计:
红黑树的实现细节:
- 红黑树是一种二叉平衡树;
- 当某个节点的左子节点为黑色,右子节点为红色,此时需要左旋;
- 当某个节点左子节点为红色,左子节点的左子节点也为红色,此时需要右旋;
- 当一个节点的左子节点为红色,右子节点也为红色,此时需要颜色反转,把左子节点和右子节点的颜色变为黑色,同时让当前节点的颜色变为红色。
红黑树的左旋:
- 让x的左子节点变为h的右子节点,h.right = x.left;
- 让h成为x的左子节点,x.left = h;
- 让h的color属性变为x的color属性,x.color = h.color;
- 让h的color属性变为red,h.color = RDE(true);
红黑树的右旋:
- 让x的右子节点变为h的左子节点,h.left = x.right;
- 让h成为x的右子节点,x.right = h;
- 让h的color变为x的color, x.color = h.color;
- 让h的color为红色,h.color = RED(true);
- 红黑树的实现代码如下:
package com.victor.tree;
import java.util.Objects;
/**
* 红黑树
*
* @description: 红黑树的实现
* @author: victor
*/
public class RedBlackTree<K extends Comparable<K>, V> {
/*指向本节点的为红色*/
private static final boolean RED = true;
/*指向本节点的为黑色*/
private static final boolean BLACK = false;
/*根节点*/
private Node root;
/*节点的个数*/
private int N;
/**
* 内部类Node节点
*/
private class Node {
final K key;
V value;
Node left;
Node right;
boolean color; //指向本节点的的颜色
public Node(K key, V value, Node left, Node right, boolean color) {
this.key = key;
this.value = value;
this.left = left;
this.right = right;
this.color = color;
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
@Override
public String toString() {
return key + "=" + value;
}
public final int hashCode() {
return Objects.hash(key, value);
}
@Override
@SuppressWarnings("unchecked")
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Node node = (Node) o;
return Objects.equals(key, node.key) && Objects.equals(value, node.value);
}
/**
* 设置新值,并返回旧值
*
* @param newValue 设置的新值
* @return 返回旧值
*/
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
}
private boolean isRed(Node node) {
//空节点默认是黑色连接
if (node == null) return BLACK;
return node.color == RED;
}
/**
* 树节点的数量
*
* @return 树的尺寸
*/
public int size() {
return N;
}
/**
* 左旋调整
*
* @param h 左旋的节点
* @return 返回的节点
*/
private Node rotateLeft(Node h) {
Node x = h.right;
h.right = x.left;
x.left = h;
x.color = h.color; //x的颜色变为h的颜色,这个颜色不确定
h.color = RED;
return x;
}
/**
* 右旋调整
*
* @param h 右旋的节点
* @return 返回的节点
*/
private Node rotateRight(Node h) {
Node x = h.left;
h.left = x.right;
x.right = h;
x.color = h.color;
h.color = RED;
return x;
}
/**
* 颜色反转
*
* @param h 反转的节点
*/
private void flipColors(Node h) {
h.left.color = BLACK;
h.right.color = BLACK;
h.color = RED;
}
/**
* 在整个树上完成插入操作
*
* @param key 键
* @param value 值
*/
public void put(K key, V value) {
root = put(root, key, value);
root.color = BLACK;
}
/**
* 在指定树中完成插入操作,返回插入后的新树
*
* @param h h树
* @param key 键
* @param value 值
* @return 插入后的新树
*/
private Node put(Node h, K key, V value) {
if (h == null) return new Node(key, value, null, null, RED);
int result = key.compareTo(h.key);
if (result < 0) h.left = put(h.left, key, value);
else if (result > 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查值
*
* @param key 键
* @return 值
*/
public V get(K key) {
return get(root, key);
}
/**
* 从指定树中,根据key查找值
*
* @param h h树
* @param key 键
* @return 值
*/
private V get(Node h, K key) {
if (h == null) return null;
int result = key.compareTo(h.key);
if (result < 0) return get(h.left, key);
else if (result > 0) return get(h.right, key);
else return h.value;
}
}
注意事项:
- 颜色反转要在左旋和右旋之后进行,因为右旋之后就会导致该节点的左子节点与右子节点为红色,此时要进行颜色反转。
- 根节点每次在插入元素之后都要让根节点的颜色变为黑色,因为树在旋转的时候有可能会导致根节点的颜色为红色。
- 在左旋或右旋时,x.color不一定为黑色,有可能为红色,因此 x.color = h.color; x的颜色变为h的颜色,这个颜色不确定。