什么是红黑树
红黑树依然是一棵二分搜索树,《算法导论》中的红黑树定义如下:
- 每个节点或者是红色的,或者是黑色的
- 根节点是黑色的
- 每一个叶子节点(最后的空节点)是黑色的
- 如果一个节点是红色的,那么他的孩子节点都是黑色的
- 从任意一个节点到叶子节点,经过的黑色节点是一样的
在学习红黑树之前,我们有必要先学习一下什么是2-3树,学习2-3树不仅对于理解红黑树有帮助,对于理解B类树,也是有巨大帮助的。我们常用到的磁盘存储、文件系统、数据库等相应的数据存储都是采用的B类树这样的数据结构。
什么是2-3树
2-3树依然满足二分搜索树的基本性质,但2-3树不是一种二叉树,2-3树有两种节点,节点可以存放一个元素或者两个元素;2-3树是一棵绝对平衡的树,2-3树对于任意一个节点的的左右子树的高度一定是相等的
2-3树的添加操作
2-3树的添加操作将新节点添加到空的位置,若添加一个比根节点小的元素,并且根节点的左子树为空,待添加的新元素会和根节点先融合,由二节点变成三节点,当此时再添加一个元素时,会发现根节点的左子树仍然为空,就还是会先和根节点向融合,但2-3树不能有四节点,最多只能有三节点,所以需要将这个四节点分裂成有三个二节点的绝对平衡二叉树,如图:
让我们再来看一下2-3树添加元素的过程:
再向2-3树中添加一些元素,找出规律:
通过如上的分析,不难知道,如果添加一个元素是添加到一个2-节点,会直接与之融合,如果是添加到一个3-节点,会暂时融合形成一个四节点,然后分裂成一个绝对平衡树。如下图所示:
红黑树与2-3树的等价性
我们在这里定义所有的红色节点都是向左倾斜的,红色节点代表与父亲节点相融合,由于我们可以通过2-3树画出一个棵红黑树:
由此可知,红黑树是保持“黑平衡”的二叉树,严格意义上 ,不是平衡二叉树,最大高度为2logn,并且从图中也可以看出,只有三节点左侧的元素才是红色的。红黑树和AVL树:由于红黑树的最大高度是2logn,所以在查找时,相比于AVL树会慢一些,而红黑树的添加和删除元素比AVL树更快一些,如果只是用于查询,AVL树的性能要更高一些。 向红黑树中添加一个新元素,类比于2-3树中添加一个新元素,就是或者添加进2-节点,形成3-节点;或者添加进3-节点,暂时形成一个4-节点,这样我们可以让我们的红黑树,永远添加红节点。由于我们在本文是定义的所有红色节点都是向左倾斜的,当我们新添加的红色节点在根节点的右侧时,我们需要先进行左旋转擦欧总,然后再进行染色操作,在我们左旋转的过程中并不保持红黑树的性质,如下图:
左旋转的代码实现:
// 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; | |
return x; | |
} |
当我们向红黑树中的“3-节点”添加新元素,由于添加的新节点颜色都默认是红色的,红色节点表示是去和 父亲节点融合的,当4-节点分裂成3个2-节点时,新的根节点需要和父亲节点去融合,这意味着这个新的根节点需要变成红色节点。
颜色翻转的代码实现:
// 颜色翻转 | |
private void flipColors(Node node){ | |
node.color = RED; | |
node.left.color = BLACK; | |
node.right.color = BLACK; | |
} |
三个2-节点的自平衡,如图:
右旋转代码实现:
// 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 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; | |
} | |
// 判断节点node的颜色 | |
private boolean isRed(Node node){ | |
if(node == null) | |
return BLACK; | |
return node.color; | |
} | |
// 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; | |
return x; | |
} | |
// 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; | |
} | |
// 颜色翻转 | |
private void flipColors(Node node){ | |
node.color = RED; | |
node.left.color = BLACK; | |
node.right.color = BLACK; | |
} | |
// 向红黑树中添加新的元素(key, value) | |
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 // 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所在的节点 | |
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为根的二分搜索树的最小值所在的节点 | |
private Node minimum(Node node){ | |
if(node.left == null) | |
return node; | |
return minimum(node.left); | |
} | |
// 删除掉以node为根的二分搜索树中的最小节点 | |
// 返回删除节点后新的二分搜索树的根 | |
private Node removeMin(Node node){ | |
if(node.left == null){ | |
Node rightNode = node.right; | |
node.right = null; | |
size --; | |
return rightNode; | |
} | |
node.left = removeMin(node.left); | |
return node; | |
} | |
// 从二分搜索树中删除键为key的节点 | |
public V remove(K key){ | |
Node node = getNode(root, key); | |
if(node != null){ | |
root = remove(root, key); | |
return node.value; | |
} | |
return null; | |
} | |
private Node remove(Node node, K key){ | |
if( node == null ) | |
return null; | |
if( key.compareTo(node.key) < 0 ){ | |
node.left = remove(node.left , key); | |
return node; | |
} | |
else if(key.compareTo(node.key) > 0 ){ | |
node.right = remove(node.right, key); | |
return node; | |
} | |
else{ // key.compareTo(node.key) == 0 | |
// 待删除节点左子树为空的情况 | |
if(node.left == null){ | |
Node rightNode = node.right; | |
node.right = null; | |
size --; | |
return rightNode; | |
} | |
// 待删除节点右子树为空的情况 | |
if(node.right == null){ | |
Node leftNode = node.left; | |
node.left = null; | |
size --; | |
return leftNode; | |
} | |
// 待删除节点左右子树均不为空的情况 | |
// 找到比待删除节点大的最小节点, 即待删除节点右子树的最小节点 | |
// 用这个节点顶替待删除节点的位置 | |
Node successor = minimum(node.right); | |
successor.right = removeMin(node.right); | |
successor.left = node.left; | |
node.left = node.right = null; | |
return successor; | |
} | |
} | |
} |
最后,在这里做一个红黑树的总结: 对于完全随机的数据,普通的二分搜索树就很好用,缺点:极端情况会退化成链表(或者高度布不平衡);对于查询较多的情况,AVL树很好用!红黑树牺牲了平衡性(2logn的高度),统计性能更优(综和增删改查所有的操作)。