2-3树
- 满足二分搜索树的基本性质
- 节点可以存放一个元素或者两个元素
- 每个节点可以有2个孩子(二节点) 或者3个孩子(三节点)
- 绝对平衡的树(从根节点到任意一个叶子节点所通过的节点数量一定是相同的)
2-3树保持平衡
添加节点永远不会去空的节点,会和最后找到的叶子节点做融合。
红黑树
等价于2-3树
- 保持黑平衡的二叉树,左右子树的黑色节点的高度差保持绝对平衡,2-3树本身是保持绝对平衡的
- 每个节点或者是红色的,后者是黑色的
- 根节点是黑色的
- 每一个叶子节点(最后的空节点)是黑色的
- 如果一个节点是红色,它的孩子节点都是黑色,黑色节点的右孩子一定是黑色的
- 从任意一个节点出发到叶子节点,经过的黑色节点是一样的
- 所有的红色节点向左倾斜
- 最大高度:2logn
//红黑树基于二分搜索树
public class RedBlackTree <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;//因为在2-3树中添加的节点永远都是要融合到已有的节点中
}
}
private Node root;
private int size;
public RedBlackTree(){
root = null;
size = 0;
}
public int getSize() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
//判断node颜色
private boolean isRed(Node node) {
if (node == null)
return Black;
return node.color;
}
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 x
// / \ 左旋转 / \
// T1 x -----------> node T3
// / \ / \
// T2 T3 T1 T2
//左旋转
private Node leftRotate(Node node) {
Node x = node.right;
//左旋转
node.right = x.left;
x.left = node;
x.color = node.color;
node.color = RED; //是2-3树中的三节点
return x;
}
- 颜色翻转,添加66,因为42需要继续向上融合(向红黑树中的3节点添加元素)
颜色翻转代码
private void flipColors(Node node) {
node.color = RED; //由于需要继续向上融合 所以是红色
node.left.color = Black; // 三个二节点 是黑色
node.right.color = Black; // 三个二节点 是黑色
}
- 右旋转,需要翻转颜色
右旋转代码
//右旋转
// node x
// / \ 右旋转 / \
// x T2 --------------> y node
// / \ / \
// y T1 T1 T2
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;
}
-
先左旋转在右旋转在翻转颜色
添加元素过程总结
//向红黑树中添加元素
public void add(K key, V value) {
root = add(root, key, value);
root.color = Black; //最终的根节点为黑色节点
}
//以向node为根的红黑树中插入元素(key, value), 递归算法
//返回插入新节点后红黑树的根
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 // ==
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;
}
性能总结
对于完全随机的数据,普通的二分搜索树很好用
缺点:极端情况下退化成链表(或者高度不平衡)
对于查询较多的使用情况,AVL树很好用
红黑树牺牲了平衡性(2logn的高度)
红黑树统计性能更优(综合增删改查所有的操作),平均性能优于AVL树。
数据结构中经常发生添加,删除的情况时,红黑树更合适。查询并不占优势。
如果数据近乎不会动的话,只查询,AVL树性能更好。