一. 红黑树的定义
- 1.每个节点或者是红色的,或者是黑色的
- 2.根节点是黑色的(一棵空树也是红黑树)。
- 3.每个叶子节点(最后的空节点)是黑色的,定义空节点都是黑色的。
- 4.如果一个节点是红色的,那么他的孩子节点都是黑色的。
- 5.从任意一个节点到叶子节点,经过的黑色节点是一样的。
学习红黑树之前,必须先学2-3树
二. 2-3树
学习2-3树,不仅对于理解红黑树有帮助,对于B树的数据结构也有帮助。
- 2-3 树满足二分搜索树的基本性质,2-3树不是二叉树。
- 节点可以存放一个元素活着2个元素。
- 2-3树是一颗绝对平衡的树:任何一个节点左右子树的高度一定是相同的。
如何维护2-3的平衡
三. 红黑树和2-3树的等价性
红黑树是保持“黑”平衡的二叉树,严格意义上,不是平衡二叉树,最大高度为2logN O(logN)
AVL的高度logN. 比AVL树的高度高,查询的时候比AVL慢一点. 如果只查询的时候,AVL快一点。
五. 红黑树添加新元素
2-3树中添加一个新元素: 或者添加晋2-节点,形成3-节点,或者添加进3-节点,暂时形成一个4-节点。
永远添加红色节点。然后维护红黑树。
添加节点之后,保持最终根节点的颜色为黑色。
红黑树的左旋转 、右旋转、颜色翻转
红黑树的添加
总结的流程如下:维护的时机和AVL树一样,添加节点后回溯向上维护。
六. 红黑树的Java简单实现
package com.mk.coffee.test.dataStructure.redblackTree;
/**
* 红黑树 《算法导论》:
* <p>
* 1.每个节点或者是红色的,或者是黑色的
* 2.根节点是黑色的(一棵空树也是红黑树)。
* 3.每个叶子节点(最后的空节点)是黑色的,定义空节点都是黑色的。
* 4.如果一个节点是红色的,那么他的孩子节点都是黑色的。连接的可能2/3节点,都是黑色的。
* 5.从任意一个节点到叶子节点,经过的黑色节点是一样的。(2-3树绝对平衡,经过的节点个数相同)
* 黑色节点的右孩子节点一定是黑色的。
* <p>
* 《算法4》 红黑树的发明人 Robert Sedgewick 是 Donald Knuth的学生。
* <p>
* 红黑树与 2-3 树的等价性
* 理解2-3树和红黑树之间的关系。
* 红色节点都是左孩子
* 红黑树是保持"黑平衡"的二叉树,严格意义来说不是平衡二叉树。
* 节点数 为N,最大高度2logN,O(logN)
* 红黑树比AVL 查找慢一点 ,红黑树2logN, AVLlogN
* <p>
* 添加的情况下,RBT好 AVL 28S RBT20S
* 查询的情况下:AVL好
* <p>
* Java java.util.中的 TreeMap 和 TreeSet 基于红黑树
* 红黑树的其他实现。
* 总结:
* 对于完全随机的数据,普通的二分搜索树就很好用!
* 确定:极端的情况退化成链表(或者高度不平衡)
* 对于查询较多的情况下,AVL树很好用!
* 红黑树牺牲了平衡性(2logN的高度)
* 统计性能更优(综合增删改查所有的操作,平均性能,红黑树性能最高)
* <p>
* <p>
* 更多的问提
* <p>
* <p>
* 伸展树Splay Tree:局部性原理: 刚被访问的内容下次高概率被再次访问。
*
* @author makui
* @date 2020/03/26
*/
public class RedBlackTree<K extends Comparable<K>, V> {
//RED BLACk 代表红黑色
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;
this.left = null;
this.right = null;
//默认是红色 等价2-3树添加节点永远是先和节点融合,然后在做操作
color = RED;
}
}
private Node root;
private int size;
public RedBlackTree() {
root = null;
size = 0;
}
//传入的Node左旋转 返回旋转后的根节点
//左旋转
// T1 < y < T2 < x < T3 < z < T4
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;
}
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;
}
/**
* 向红黑树添加新的元素(key,value)
* 保持根节点的颜色为黑色
*
* @param key
* @param value
*/
public void add(K key, V value) {
root = add(root, key, value);
root.color = BLACK;
}
/**
* 向node为根的红黑色中添加元素(key,value),递归算法
* 返回插入新节点后红黑树的根
*
* @param node
* @param key
* @param 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
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)) {
//转换颜色
flipColor(node);
}
return node;
}
private void flipColor(Node node) {
node.color = true;
node.left.color = false;
node.right.color = false;
}
private boolean isRed(Node node) {
return node.color;
}
/**
* 返回以node为根节点的二分搜索树中,key所在的节点
*
* @param node
* @param key
* @return
*/
private Node getNode(Node node, K key) {
if (node == null) {
return null;
}
if (key.compareTo(node.key) == 0) {
return node;
} else if (key.compareTo(node.key) < 0) {
return getNode(node.left, key);
} else {
return getNode(node.right, key);
}
}
public V remove(K key) {
//todo 仿照BST 自己写
return null;
}
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;
}
/**
* 查找二分查找树的最小元素
*
* @return
*/
public V minimum() {
if (size == 0) {
throw new IllegalArgumentException("BST is empty!");
}
return minimum(root).value;
}
/**
* 最小值以node为根的二分搜索树
*
* @param node
* @return
*/
private Node minimum(Node node) {
if (node.left == null) {
return node;
}
return minimum(node.left);
}
/**
* 查找二分查找树的最大元素
*
* @return
*/
public V maximum() {
if (size == 0) {
throw new IllegalArgumentException("BST is empty!");
}
return maximum(root).value;
}
/**
* 最大值以node为根的二分搜索树
*
* @param node
* @return
*/
private Node maximum(Node node) {
if (node.right == null) {
return node;
}
return minimum(node.right);
}
public int getSize() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
}
七. 红黑树和AVL对比
如果插入一个node引起了树的不平衡,AVL和RB-Tree都是最多只需要2次旋转操作,即两者都是O(1);但是在删除node引起树的不平衡时,最坏情况下,AVL需要维护从被删node到root这条路径上所有node的平衡性,因此需要旋转的量级O(logN),而RB-Tree最多只需3次旋转(左旋转,右旋转,颜色翻转),只需要O(1)的复杂度。
其次,AVL的结构相较RB-Tree来说更为平衡,在插入和删除node更容易引起Tree的unbalance,因此在大量数据需要插入或者删除时,AVL需要rebalance的频率会更高。因此,RB-Tree在需要大量插入和删除node的场景下,效率更高。自然,由于AVL高度平衡,因此AVL的search效率更高。
map的实现只是折衷了两者在search、insert以及delete下的效率。总体来说,RB-tree的统计性能是高于AVL的。