一. 紅黑樹的定義
- 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的。