紅黑樹是一種平衡的二叉樹,其在二分搜索樹的基礎上添加了一些特性,保證其不會退化成鏈表。java
說明:以上紅黑樹的定義看完很快就會忘記,疑惑點在於爲何要這麼定義。爲何有的節點是紅色或是黑色。爲何這麼幾條限制就能保證紅黑樹的平衡性。這些疑問點在看完下面對於「2-3樹」的介紹後會逐漸變得明朗,「2-3樹」和紅黑樹是等價的,紅黑樹的各個特性能夠類比於「2-3樹」。node
2-3樹是一顆絕對平衡的樹,從根節點到任意葉子節點所應該的節點數量是相等的;它知足二分搜索樹的基本性質;有的節點僅包含一個元素,它容許有2個孩子節點,被稱爲「2節點」;有的節點包含兩個元素,它容許有三個孩子節點,被稱爲「3節點」。
算法
說明:元素添加到2-3樹的2節點直接融合成一個3節點便可。性能
說明:元素添加到2-3樹的3節點會臨時融合成「4節點」,而後會裂變成三個2節點,以其中一個節點爲根,裂變後仍然知足二分搜索樹的性質。this
說明:元素添加到2-3樹的3節點會臨時融合成「4節點」,而後會裂變成三個2節點,以其中一個節點爲根;此臨時4節點裂變後產生的新子樹高度增長了1,破壞了絕對平衡性,因而這顆新子樹的根鬚要繼續向其父親節點融合,融合後新子樹增長的高度被抹平,從新保持絕對平衡。spa
說明:2-3樹維護絕對平衡,靠的就是融合、裂變的操做,融合後樹的高度保持變後者高度下降,而裂變操做後會增長樹的高度,所以裂變後會伴隨着融和操做,融合操做中和裂變操做。從而保證了2-3的絕對平衡。3d
說明:紅黑樹之因此定義節點的紅黑顏色,實際上就是模擬2-3樹的2節點或者3節點。咱們再來回顧一下關於開頭的紅黑樹定義,下面會作個簡單說明。code
說明:模擬2-3樹的2節點和3節點只須要兩種顏色便可,紅色節點表示與其父親節點融合。blog
說明:根節點做爲紅黑樹的根,不須要向上融合了。繼承
說明:由於一個紅色節點與其父親節點已經組成了相似於2-3樹的3節點了,若是其孩子節點依然是紅色的,那就構成了2-3樹中的臨時「4節點」,此時2-3樹要進行裂變和融合操做保證絕對平衡性。那麼相似於2-3樹,紅黑樹有連續兩個節點是紅色的話,紅黑樹須要進行旋轉操做(相似於2-3樹的裂變操做)配合顏色翻轉操做(相似於2-3樹的融合操做),以使得紅黑樹保持平衡性。連續連續兩個紅色節點也是紅黑樹出發自平衡的觸發點,因此不會出現連續兩個紅色節點。
說明:因爲紅黑樹是模擬於2-3樹,而2-3樹是絕對平衡的樹,因此除去表示融合的紅色節點,紅黑樹中的黑色節點也是絕對平衡的,紅黑樹也被稱爲是「黑平衡」的二叉樹,所以從紅黑樹的任意節點出發到葉子節點,所經歷的黑色節點數是一致的。
紅黑樹在添加元素、刪除元素後會影響樹的平衡性,可能會破壞紅黑樹的定義。相似於2-3樹的平衡操做(裂變節點、融合節點),紅黑樹也有其平衡操做,左右旋轉相似於2-3樹的裂變節點;顏色翻轉相似於2-3樹的融合節點。
說明:如下會實現一個左傾紅黑樹,在遞歸算法實現的紅黑樹添加操做中,當遞歸到深層的葉子節點融合元素(紅色節點)後可能形成不平衡,須要遞歸逐層向上維護節點狀態。
說明:左傾紅黑樹規定節點的右孩子不能是紅色節點,須要左旋操做。
說明:左旋操做後當前子樹的根節點發生了變化,當前的根節點須要繼承保留原有根節點的顏色,同時原來的根節點變成紅色節點。若是原有根節點是紅色節點,那麼上層須要繼續進行相關操做。
說明:連續兩個節點爲紅色節點,違反了紅黑樹的定義,相似於造成了2-3樹的臨時4節點,此時紅黑樹的平衡被破壞,須要再平衡操做。
說明:右旋操做後,發現節點狀態依然相似於2-3樹的臨時4節點狀態,須要繼續裂變和融合操做,而紅黑樹此種場景只須要向上融合便可,即須要顏色翻轉,使x節點變成紅色節點表示向上融合。
總結:紅黑樹模擬於2-3樹也等價於2-3樹。紅黑樹的左右旋轉等價於2-3樹的裂變節點操做;紅黑樹的顏色翻轉等價於2-3樹的融合節點操做;不一樣點在於紅黑樹的左右旋轉會能夠下降子樹的高度,而2-3樹的裂變節點操做會增長子樹的高度;紅黑樹的顏色翻轉操做不會改變子樹的高度,是一個抽象的融合操做,而2-3樹的融合操做能夠下降子樹高度。最終紅黑樹也會趨於平衡,而2-3會保持絕對平衡。
import java.util.ArrayList; 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; } /** * 判斷節點是不是紅色 * @param node 樹節點 * @return */ private boolean isRed(Node node) { if (node == null) { return BLACK; } return node.color; } /** * 節點左旋轉操做 * node x * / \ 左旋轉 / \ * T1 x ---------> node T3 * / \ / \ * T2 T3 T1 T2 * @param node * @return */ 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 * @param node * @return */ 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; } /** * 顏色翻轉 * @param node */ private void flipColors(Node node) { node.color = RED; node.left.color = BLACK; node.right.color = BLACK; } /** * 向紅黑樹中添加新的元素(key, value) * @param key 元素key * @param value 元素value */ public void add(K key, V value){ root = add(root, key, value); root.color = BLACK;//最終根節點爲黑色節點 } /** * 向以node爲根的二分搜索樹中插入元素(key, value),遞歸算法 * 返回插入新節點後二分搜索樹的根 * @param node 樹節點 * @param key 元素key * @param value 元素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 // 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所在的節點 * @param node 節點 * @param key 元素key * @return */ 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爲根的二分搜索樹的最小值所在的節點 * @param node * @return */ private Node minimum(Node node){ if(node.left == null) return node; return minimum(node.left); } }
紅黑樹相比於AVL樹,實際上是犧牲了平衡性的,紅黑樹並不徹底知足平衡二叉樹的定義,紅黑樹的最大高度達到了2logn的高度,紅色節點影響了紅黑樹的的平衡性。紅黑樹雖然犧牲了必定的查詢性能,可是在增刪改操做的性能獲得了彌補,紅黑樹的綜合性能仍是要優於AVL樹的。