红黑树主要是对2-3树进行编码,红黑树背后的基本思想是用标准的二叉查找树(完全由2-结点构成)和一些额外的信 息(替换3-结点)来表示2-3树。我们将树中的链接分为两种类型: 红链接:将两个2-结点连接起来构成一个3-结点; 黑链接:则是2-3树中的普通链接。 确切的说,我们将3-结点表示为由由一条左斜的红色链接(两个2-结点其中之一是另一个的左子结点)相连的两个2- 结点。这种表示法的一个优点是,我们无需修改就可以直接使用标准的二叉查找树的get方法。
红黑树是含有红黑链接并满足下列条件的二叉查找树: 1. 红链接均为左链接; 2. 没有任何一个结点同时和两条红链接相连; 3. 该树是完美黑色平衡的,即任意空链接到根结点的路径上的黑链接数量相同;
下面是红黑树与2-3树的对应关系:
-- 红黑树平衡化操作:
1、左旋:当前结点左链接为黑色,右连接为红色时,需要左旋
private Node rotateLeft(Node h) {
//找出当前结点h的右子结点
Node hRight = h.right;
//让当前结点h的右子结点的左子结点成为当前结点的右子结点
h.right = hRight.left;
//让当前结点h称为右子结点的左子结点
hRight.left = h;
//让当前结点h的color成为右子结点的color
hRight.color = h.color;
//让当前结点h的color变为RED
h.color = RED;
//返回当前结点的右子结点
return hRight;
}
2、右旋:当前结点的左子结点的左子结点链接都是红色,需要右旋
private Node rotateRight(Node h) {
//找出当前结点h的左子结点
Node hLeft = h.left;
//让当前结点h的左子结点的右子结点称为当前结点的左子结点
h.left = hLeft.right;
//让当前结点称为左子结点的右子结点
hLeft.right = h;
//让当前结点h的color值称为左子结点的color值
hLeft.color = h.color;
//让当前结点h的color变为RED
h.color = RED;
//返回当前结点的左子结点
return hLeft;
}
3、颜色反转:当前左右链接都是红色,需要颜色反转
private void flipColors(Node h) {
//当前结点的color属性值变为RED;
h.color = RED;
//当前结点的左右子结点的color属性值都变为黑色
h.left.color = BLACK;
h.right.color = BLACK;
}
-- API设计:
-- 代码:
/**
* 红黑树
*/
public class RedBlackTree<Key extends Comparable<Key>, Value> {
// 根结点
private Node root;
// 记录树中元素的个数
private int N;
// 红色链接
private static final boolean RED = true;
// 黑色链接
private static final boolean BLACK = false;
// 当前结点的父指向链接是否为红色
private boolean isRed(Node x) {
//空结点默认是黑色链接
if (x == null) {
return false;
}
//非空结点需要判断结点color属性的值
return x.color == RED;
}
// 左旋转
private Node rotateLeft(Node h) {
//找出当前结点h的右子结点
Node hRight = h.right;
//让当前结点h的右子结点的左子结点成为当前结点的右子结点
h.right = hRight.left;
//让当前结点h称为右子结点的左子结点
hRight.left = h;
//让当前结点h的color成为右子结点的color
hRight.color = h.color;
//让当前结点h的color变为RED
h.color = RED;
//返回当前结点的右子结点
return hRight;
}
// 右旋转
private Node rotateRight(Node h) {
//找出当前结点h的左子结点
Node hLeft = h.left;
//让当前结点h的左子结点的右子结点称为当前结点的左子结点
h.left = hLeft.right;
//让当前结点称为左子结点的右子结点
hLeft.right = h;
//让当前结点h的color值称为左子结点的color值
hLeft.color = h.color;
//让当前结点h的color变为RED
h.color = RED;
//返回当前结点的左子结点
return hLeft;
}
// 颜色反转
private void flipColors(Node h) {
//当前结点的color属性值变为RED;
h.color = RED;
//当前结点的左右子结点的color属性值都变为黑色
h.left.color = BLACK;
h.right.color = BLACK;
}
// 向整棵树中插入元素
public void put(Key key, Value val) {
//在root整个树上插入key-val
root = put(root, key, val);
//让根结点的颜色变为BLACK
root.color = BLACK;
}
// 向指定的树中插入元素,并返回添加元素后的新树
private Node put(Node h, Key key, Value val) {
if (h == null) {
//标准的插入操作,和父结点用红链接相连
N++;
return new Node(key, val, null, null, RED);
}
//比较要插入的键和当前结点的键
int cmp = key.compareTo(h.key);
if (cmp < 0) {
//继续寻找左子树插入
h.left = put(h.left, key, val);
} else if (cmp > 0) {
//继续寻找右子树插入
h.right = put(h.right, key, val);
} else {
//已经有相同的结点存在,修改节点的值;
h.value = val;
}
//如果当前结点的右链接是红色,左链接是黑色,需要左旋
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,从树中找出对应的值
public Value get(Key key) {
return get(root, key);
}
//从指定的树x中,查找key对应的值
public Value get(Node x, Key key) {
//如果当前结点为空,则没有找到,返回null
if (x == null) {
return null;
}
//比较当前结点的键和key
int cmp = key.compareTo(x.key);
if (cmp < 0) {
//如果要查询的key小于当前结点的key,则继续找当前结点的左子结点;
return get(x.left, key);
} else if (cmp > 0) {
//如果要查询的key大于当前结点的key,则继续找当前结点的右子结点;
return get(x.right, key);
} else {
//如果要查询的key等于当前结点的key,则树中返回当前结点的value。
return x.value;
}
}
//获取树中元素的个数
public int size() {
return N;
}
//结点类
private class Node {
//存储键
public Key key;
//存储值
private Value value;
//记录左子结点
public Node left;
//记录右子结点
public Node right;
//由其父结点指向它的链接的颜色
public boolean color;
public Node(Key key, Value value, Node left, Node right, boolean color) {
this.key = key;
this.value = value;
this.left = left;
this.right = right;
this.color = color;
}
}
}
@ 以上内容属于个人笔记