一、引言
红黑树是一种平衡的二叉树,其在二分搜索树的基础上添加了一些特性,保证其不会退化成链表。
1、红黑树的定义
- 满足二分搜索树的基本性质
- 每个节点要么是红色的,要么是黑色的
- 根节点是黑色的
- 每一个叶子节点(最后的空节点)是黑色的
- 如果一个节点是红色的,那么它的孩子节点都是黑色的
- 从任意一个节点到叶子节点,经过的黑色节点是一样的
说明:以上红黑树的定义看完很快就会忘记,疑惑点在于为什么要这么定义。为什么有的节点是红色或是黑色。为什么这么几条限制就能保证红黑树的平衡性。这些疑问点在看完下面对于“2-3树”的介绍后会逐渐变得明朗,“2-3树”和红黑树是等价的,红黑树的各个特性可以类比于“2-3树”。
二、2-3树
1、什么是2-3树
2-3树是一颗绝对平衡的树,从根节点到任意叶子节点所应该的节点数量是相等的;它满足二分搜索树的基本性质;有的节点仅包含一个元素,它允许有2个孩子节点,被称为“2节点”;有的节点包含两个元素,它允许有三个孩子节点,被称为“3节点”。
2、2-3树的定义
- 满足二分搜索树的基本性质
- 节点可以存放一个元素或者两个元素
- 每个节点有2个(2节点)后者3个孩子(3节点)
3、2-3树维持绝对平衡
1、元素添加到2节点
说明:元素添加到2-3树的2节点直接融合成一个3节点即可。
2、元素添加到3节点
说明:元素添加到2-3树的3节点会临时融合成“4节点”,然后会裂变成三个2节点,以其中一个节点为根,裂变后仍然满足二分搜索树的性质。
3、元素添加到3节点,且父亲节点为2节点
说明:元素添加到2-3树的3节点会临时融合成“4节点”,然后会裂变成三个2节点,以其中一个节点为根;此临时4节点裂变后产生的新子树高度增加了1,破坏了绝对平衡性,于是这颗新子树的根需要继续向其父亲节点融合,融合后新子树增加的高度被抹平,重新保持绝对平衡。
4、元素添加到3节点,且父亲节点为3节点
说明:2-3树维护绝对平衡,靠的就是融合、裂变的操作,融合后树的高度保持变后者高度降低,而裂变操作后会增加树的高度,因此裂变后会伴随着融和操作,融合操作中和裂变操作。从而保证了2-3的绝对平衡。
三、红黑树和2-3树
1、红黑树与2-3树的等价性
- 红黑树用黑色节点表示2-3树的2节点
- 红黑树用红色节点表示与其祖先节点融合组成3节点
说明:红黑树之所以定义节点的红黑颜色,实际上就是模拟2-3树的2节点或者3节点。我们再来回顾一下关于开头的红黑树定义,下面会做个简单说明。
- 为什么每个节点要么是红色的,要么是黑色的?
说明:模拟2-3树的2节点和3节点只需要两种颜色即可,红色节点表示与其父亲节点融合。
- 为什么根节点是黑色的?
说明:根节点作为红黑树的根,不需要向上融合了。
- 为什么一个节点是红色的,那么它的孩子节点都是黑色的?
说明:因为一个红色节点与其父亲节点已经组成了类似于2-3树的3节点了,如果其孩子节点依然是红色的,那就构成了2-3树中的临时“4节点”,此时2-3树要进行裂变和融合操作保证绝对平衡性。那么类似于2-3树,红黑树有连续两个节点是红色的话,红黑树需要进行旋转操作(类似于2-3树的裂变操作)配合颜色翻转操作(类似于2-3树的融合操作),以使得红黑树保持平衡性。连续连续两个红色节点也是红黑树出发自平衡的触发点,所以不会出现连续两个红色节点。
- 为什么从任意一个节点到叶子节点,经过的黑色节点是一样的?
说明:由于红黑树是模拟于2-3树,而2-3树是绝对平衡的树,所以除去表示融合的红色节点,红黑树中的黑色节点也是绝对平衡的,红黑树也被称为是“黑平衡”的二叉树,因此从红黑树的任意节点出发到叶子节点,所经历的黑色节点数是一致的。
四、红黑树相关操作
红黑树在添加元素、删除元素后会影响树的平衡性,可能会破坏红黑树的定义。类似于2-3树的平衡操作(裂变节点、融合节点),红黑树也有其平衡操作,左右旋转类似于2-3树的裂变节点;颜色翻转类似于2-3树的融合节点。
说明:以下会实现一个左倾红黑树,在递归算法实现的红黑树添加操作中,当递归到深层的叶子节点融合元素(红色节点)后可能造成不平衡,需要递归逐层向上维护节点状态。
1、红黑树左旋操作
- 左倾左旋操作前
说明:左倾红黑树规定节点的右孩子不能是红色节点,需要左旋操作。
- 左旋操作前后
说明:左旋操作后当前子树的根节点发生了变化,当前的根节点需要继承保留原有根节点的颜色,同时原来的根节点变成红色节点。如果原有根节点是红色节点,那么上层需要继续进行相关操作。
2、红黑树的右旋操作
- 右旋操作前
说明:连续两个节点为红色节点,违反了红黑树的定义,类似于形成了2-3树的临时4节点,此时红黑树的平衡被破坏,需要再平衡操作。
- 右旋操作后
说明:右旋操作后,发现节点状态依然类似于2-3树的临时4节点状态,需要继续裂变和融合操作,而红黑树此种场景只需要向上融合即可,即需要颜色翻转,使x节点变成红色节点表示向上融合。
3、颜色翻转操作
- 颜色翻转前
- 颜色翻转后
总结:红黑树模拟于2-3树也等价于2-3树。红黑树的左右旋转等价于2-3树的裂变节点操作;红黑树的颜色翻转等价于2-3树的融合节点操作;不同点在于红黑树的左右旋转会可以降低子树的高度,而2-3树的裂变节点操作会增加子树的高度;红黑树的颜色翻转操作不会改变子树的高度,是一个抽象的融合操作,而2-3树的融合操作可以降低子树高度。最终红黑树也会趋于平衡,而2-3会保持绝对平衡。
五、红黑树的实现
1、左倾红黑树的实现
import java.util.ArrayList;
public class RBTree<K extends Comparable<K>, V> {
private static final boolean RED = true;
private static final boolean BLACK = false;
private class Node{
public K key;
public V value;
public Node left, right;
public boolean color;
public Node(K key, V value){
this.key = key;
this.value = value;
left = null;
right = null;
color = RED;
}
}
private Node root;
private int size;
public RBTree(){
root = null;
size = 0;
}
public int getSize(){
return size;
}
public boolean isEmpty(){
return size == 0;
}
/**
* 判断节点是否是红色
* @param node 树节点
* @return
*/
private boolean isRed(Node node) {
if (node == null) {
return BLACK;
}
return node.color;
}
/**
* 节点左旋转操作
* node x
* / \ 左旋转 / \
* T1 x ---------> node T3
* / \ / \
* T2 T3 T1 T2
* @param node
* @return
*/
private Node leftRotate(Node node) {
Node x = node.right;
//左旋转
node.right = x.left;
x.left = node;
//颜色维护
x.color = node.color;
node.color = RED;
return x;
}
/**
* 右旋转操作
* node x
* / \ 右旋转 / \
* x T2 -------> y node
* / \ / \
* y T1 T1 T2
* @param node
* @return
*/
private Node rightRotate(Node node) {
Node x = node.left;
//右旋转
node.left = x.right;
x.right = node;
//颜色维护
x.color = node.color;
node.color = RED;
return x;
}
/**
* 颜色翻转
* @param node
*/
private void flipColors(Node node) {
node.color = RED;
node.left.color = BLACK;
node.right.color = BLACK;
}
/**
* 向红黑树中添加新的元素(key, value)
* @param key 元素key
* @param value 元素value
*/
public void add(K key, V value){
root = add(root, key, value);
root.color = BLACK;//最终根节点为黑色节点
}
/**
* 向以node为根的二分搜索树中插入元素(key, value),递归算法
* 返回插入新节点后二分搜索树的根
* @param node 树节点
* @param key 元素key
* @param value 元素value
* @return 返回插入新节点后二分搜索树的根
*/
private Node add(Node node, K key, V value){
if(node == null){
size ++;
return new Node(key, value);
}
if(key.compareTo(node.key) < 0)
node.left = add(node.left, key, value);
else if(key.compareTo(node.key) > 0)
node.right = add(node.right, key, value);
else // key.compareTo(node.key) == 0
node.value = value;
//左旋操作
if (isRed(node.right) && !isRed(node.left)) {
node = leftRotate(node);
}
//右旋操作
if (isRed(node.left) && isRed(node.left.left)) {
node = rightRotate(node);
}
//颜色翻转
if (isRed(node.left) && isRed(node.right)) {
flipColors(node);
}
return node;
}
/**
* 返回以node为根节点的二分搜索树中,key所在的节点
* @param node 节点
* @param key 元素key
* @return
*/
private Node getNode(Node node, K key){
if(node == null)
return null;
if(key.equals(node.key))
return node;
else if(key.compareTo(node.key) < 0)
return getNode(node.left, key);
else // if(key.compareTo(node.key) > 0)
return getNode(node.right, key);
}
public boolean contains(K key){
return getNode(root, key) != null;
}
public V get(K key){
Node node = getNode(root, key);
return node == null ? null : node.value;
}
public void set(K key, V newValue){
Node node = getNode(root, key);
if(node == null)
throw new IllegalArgumentException(key + " doesn't exist!");
node.value = newValue;
}
/**
* 返回以node为根的二分搜索树的最小值所在的节点
* @param node
* @return
*/
private Node minimum(Node node){
if(node.left == null)
return node;
return minimum(node.left);
}
}
六、时间复杂度分析
红黑树相比于AVL树,其实是牺牲了平衡性的,红黑树并不完全满足平衡二叉树的定义,红黑树的最大高度达到了2logn的高度,红色节点影响了红黑树的的平衡性。红黑树虽然牺牲了一定的查询性能,但是在增删改操作的性能得到了弥补,红黑树的综合性能还是要优于AVL树的。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。