AVL树和红黑树02:2-3树和红黑树
2-3树
2-3树满足二分搜索树的基本性质,中间节点的值大于左子树,小于右子树
但是,其节点可以存放1个元素或者2个元素,即每个节点有2个孩子或者3个孩子,分别称为2节点和3节点
2-3树是一种绝对平衡树,任何时候任何节点的子树高度都一样,即要么是叶子节点,要么同时有左(中)右孩子
2-3树如何维持绝对的平衡
2-3树在添加节点时,新节点永远不会去空的位置,因此从根节点开始判断,最后遇到的节点肯定是叶子节点,此时分以下两种情况:
1、如果该叶子节点是一个2节点,那么直接和新节点合并成3节点
2、如果该叶子节点是一个3节点,那么在进行上述操作后,该节点变成了“4节点”,此时分以下两种情况:
- 如果该“4节点”是根节点,那么让该节点中间的那个元素成为新的根节点,左右两个元素分别为左右孩子(树的高度增加1)
- 如果该“4节点”不是根节点,那么在进行上述操作后,其父节点的左右子树必然不平衡了,此时让该子树的根节点和父节点融合,此时分以下两种情况:
- 如果父节点是2节点,那么该子树边缘的那个孩子不动,靠近中间的那个孩子成为中间孩子
- 如果父节点是3节点,那么在进行上述操作以后,父节点变成了“4节点”,此时让父节点中间的那个元素成为新的父节点,左右两个元素分别作为左右子树的根节点(树的高度增加1)
红黑树和2-3树的等价性
2-3树的2节点对应于红黑树的普通节点,是黑色的
而3节点拥有两个元素,对应在红黑树中则是让较小的元素作为该节点的左孩子,用红色来表示,因此所有的红色节点都是左倾斜的,且和父节点的连线是红色的,表示其对应于2-3树的3节点
可以将每个红节点和父节点看成是两个平等的节点,这样整体看红黑树也是绝对平衡树
红黑树的性质
红黑树同样满足二分搜索树的基本性质,但为了维持平衡二叉树的性质,需同时满足以下5个条件:
- 每个节点有且仅能是红色或者黑色
- 根节点是黑色的(因为红节点必然有父节点)
- 每个叶子节点(最后的空节点)是黑色的
- 一个红色节点的孩子节点都是黑色的(因为红节点的父节点必然是黑节点)
- 从任意一个节点出发到叶子节点,经过的黑色节点都是一样的(等价于2-3树的绝对平衡性,在红黑树中称为“黑平衡性”)
红黑树添加元素
红黑树删除元素非常复杂,因此没有进行介绍
保持根节点为黑色和左旋转
红黑树在添加节点时,新节点永远是红节点,因此从根节点开始判断,最后遇到的节点要么没有孩子,要么有左红孩子,此时分以下两种情况:
1、如果当前节点没有孩子,此时分以下两种情况:
- 如果小于当前节点,那么直接作为其左孩子
- 如果大于当前节点,那么先作为右孩子,然后对其进行左旋转,同时颜色发生改变,新树的左孩子为红节点,父节点颜色和左旋转之前的父节点一样
/**
* 左旋转,然后改变颜色,此时的父节点x和之前的父节点node颜色一样
* node x
* / \ / \
* T1 x 左旋转(node) 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;
return x;
}
public void add(K key, V value){
/**
* 添加元素后,保持根节点为黑色
*/
root = add(root, key, value);
root.color = BLACK;
}
......
颜色翻转和右旋转
2、如果当前节点有左红孩子,此时分以下两种情况:
- 如果大于当前节点,那么直接作为其右孩子,且左右孩子节点颜色全部翻转为黑色,父节点为红色
- 如果小于当前节点,此时分以下两种情况:
- 如果小于左孩子,那么先作为左孩子的左孩子,然后对当前节点进行右旋转,同时颜色发生改变,新树的右孩子为红节点,父节点颜色和右旋转之前的父节点一样。最后为了维持红黑树的性质,再进行一次颜色翻转
- 如果大于左孩子,那么先作为左孩子的右孩子,然后对该左孩子进行左旋转,然后对父亲节点进行右旋转,具体操作和上述一样
/**
* 右旋转,然后改变颜色,此时的父节点x和之前的父节点node颜色一样
* node x
* / \ / \
* x T2 右旋转(node) T2 node
* / \ ----------------> / \
* T2 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;
}
/**
* 翻转颜色
* 父亲节点为红色,左右孩子为黑色
*/
private void flipColors(Node node){
node.color = RED;
node.left.color = BLACK;
node.right.color = BLACK;
}
......
添加元素完整代码
class redBlackTree, V>{
/**
* 红黑树的节点需要一个判断颜色的属性,为了避免被修改,定义两个静态常量RED和BLACK来记录颜色,红色代表true
*/
private static final boolean RED = true;
private static final boolean BLACK = false;
class Node{
/**
* 为什么默认添加节点定义为红色?
* 等价于在2-3树中每个新节点都要和其他节点进行融合(不能添加在空的位置),形成3节点或者“4节点”,然后再进行维护操作
*/
public K key;
public V value;
public Node left;
public Node 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;
int size;
public redBlackTree(){
root = null;
size = 0;
}
public int getSize(){
return size;
}
public boolean isEmpty(){
return size == 0;
}
private boolean isRed(Node node){
/**
* 空节点定义为黑
*/
if (node == null){
return BLACK;
}
return node.color;
}
/**
* 左旋转,然后改变颜色,此时的父节点x和之前的父节点node颜色一样
* node x
* / \ / \
* T1 x 左旋转(node) 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;
return x;
}
/**
* 右旋转,然后改变颜色,此时的父节点x和之前的父节点node颜色一样
* node x
* / \ / \
* x T2 右旋转(node) T2 node
* / \ ----------------> / \
* T2 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;
}
/**
* 翻转颜色
* 父亲节点为红色,左右孩子为黑色
*/
private void flipColors(Node node){
node.color = RED;
node.left.color = BLACK;
node.right.color = BLACK;
}
public void add(K key, V value){
/**
* 添加元素后,保持根节点为黑色
*/
root = add(root, key, value);
root.color = BLACK;
}
private Node add(Node root, K key, V value){
if (root == null){
size++;
return new Node(key, value);
}
if (root.key.compareTo(key) > 0){
root.left = add(root.left, key, value);
}
else if (root.key.compareTo(key) < 0){
root.right = add(root.right, key, value);
}
else {
root.value = value;
}
/**
* 添加新节点后,根据下面三个判断条件来维护红黑树的性质
* 1、如果当前节点没有孩子,且新节点大于当前节点(小于的话直接添加默认就是红色),那么先作为右孩子,然后对其进行左旋转
*/
if (isRed(root.right) && !isRed(root.left)){
root = leftRotate(root);
}
/**
* 2、如果当前节点有左红孩子,且新节点小于左孩子,那么先作为左孩子的左孩子,然后对当前节点进行右旋转
*/
if (isRed(root.left) && isRed(root.left.left)){
root = rightRotate(root);
}
/**
* 3、最后判断是否要进行颜色翻转,如果左右孩子都是红色就翻转
*/
if (isRed(root.left) && isRed(root.right)){
flipColors(root);
}
return root;
}
}
红黑树的性能总结
二分搜索树 | AVL树 | 红黑树 | |
---|---|---|---|
优点 | 对于完全随机的数据,二分搜索树也很好用,而且内部实现的复杂程度小于红黑树,综合性能甚至会优于红黑树 | 对于查询操作比较多的情况,AVL树有很大的优势,因为其高度平衡,不会退化成链表 | 红黑树的统计性能更优(综合增删改查所有的操作,时间复杂度都是O(logn)) |
缺点 | 在数据有序化很高的情况下会退化成链表(高度不平衡) | 在删除操作时,旋转的次数相对于红黑树会多一点,但是差别并不大 | 牺牲了平衡性(2logn的高度) |
拓展:另一种统计性能优秀的树结构Splay Treee(伸展树),其基于局部性原理,即刚被访问的内容下次高概率被再次访问