紅黑樹是比較常見的數據結構之一,在Linux內核中的徹底公平調度器、高精度計時器、多種語言的函數庫(如,Java的TreeMap)等都有使用。java
在學習紅黑樹以前,先來熟悉一下二叉查找樹。數組
二叉查找樹,它有一個根節點,且每一個節點下最多有隻能有兩個子節點,左子節點的值小於其父節點,右子節點的值大於其父節點。數據結構
從根節點向下查找,當新插入節點大於比較的節點時,新節點插入到比較節點的右側,當小於比較的節點時,插入到比較節點的左側,一直向下比較大小,找到要插入元素的位置並插入元素。函數
如圖: 依次插入節點[100,50,200,80,300,10]學習
僞代碼(來源Java TreeMap,有省略和修改):spa
void put(K key, V value) { if (root == null) { root = new Node<>(key, value, null); return; } Node<K,V> t = root; int cmp; // 比較結果 Node<K,V> parent; Comparable<? super K> k = (Comparable<? super K>) key; do { parent = t; cmp = k.compareTo(t.key); if (cmp < 0) t = t.left; else if (cmp > 0) t = t.right; else return; // 節點存在直接返回 } while (t != null); Node<K,V> e = new Node<>(key, value, parent); if (cmp < 0){ parent.left = e; }else{ parent.right = e; } }
從根節點開始向下查找,當查找節點大於比較的節點時,向右查找,當小於當前比較節點時,就向左查找。一直向下查找,直到找到對應的節點或到終點查找結束。3d
如圖: 查找節點[80]code
僞代碼(來源Java TreeMap,有省略和修改):blog
Node<K,V> getNode(Object key) { Comparable<? super K> k = (Comparable<? super K>) key; Node<K,V> p = root; while (p != null) { int cmp = k.compareTo(p.key); if (cmp < 0) p = p.left; else if (cmp > 0) p = p.right; else return p; } return null; }
刪除節點首先要查找要刪除的節點,找到後執行刪除操做。索引
刪除節點的節點有以下幾種狀況:
- 刪除的節點有兩個子節點
- 刪除的節點有一個子節點
- 刪除的節點沒有子節點
該種狀況下,涉及到節點的「位置變換」,用右子樹中的最小節點替換當前節點。從右子樹一直 left 到 NULL。最後會被轉換爲 Case 2 或 Case 3 的狀況。
因此對於刪除有兩個孩子的節點,刪除的是其右子樹的最小節點,最小節點的內容會替換要刪除節點的內容。
如圖:刪除節點[50]
有一個子節點的狀況下,將其父節點指向其子節點,而後刪除該節點。
如圖:刪除節點[200]
在沒有子節點的狀況,其父節點指向空,而後刪除該節點。
如圖:刪除節點[70]
僞代碼(來源Java TreeMap,有省略和修改):
Node remove(Object key) { // 查找節點(參考上面查找代碼) Node<K,V> p = getNode(key); // 節點變換。 p 有兩個子節點,將其轉換爲刪除後繼節點 if (p.left != null && p.right != null) { Entry<K,V> s = t.right; while (s.left != null){ s = s.left; } p.key = s.key; p.value = s.value; p = s; } Entry<K,V> replacement = (p.left != null ? p.left : p.right); // p 有一個子節點 if (replacement != null) { replacement.parent = p.parent; if (p.parent == null){ root = replacement; } else if (p == p.parent.left){ p.parent.left = replacement; } else{ p.parent.right = replacement; } p.left = p.right = p.parent = null; } else if (p.parent == null) { // 根節點 root = null; } else { // p 沒有子節點 if (p == p.parent.left){ p.parent.left = null; } else if (p == p.parent.right){ p.parent.right = null; } p.parent = null; } return p; }
咱們知道,有序數組刪除或插入數據較慢(向數組中插入數據時,涉及到插入位置先後數據移動的操做),但根據索引查找數據很快,能夠快速定位到數據,適合查詢。而鏈表正好相反,查找數據比較慢,插入或刪除數據較快,只須要引用移動下就能夠,適合增刪。
而二叉樹就是同時具備以上優點的數據結構。
上面的樹是非平衡樹,因爲插入數據順序緣由,多個節點可能會傾向根的一側。極限狀況下全部元素都在一側,此時就變成了一個至關於鏈表的結構。
如圖:依次插入節點[100,150,170,300,450,520 ...]
這種不平衡將會使樹的層級增多(樹的高度增長),查找或插入元素效率變低。
那麼只要當插入元素或刪除元素時還能維持樹的平衡,使元素不至於向一端嚴重傾斜,就能夠避免這個問題。
到此,紅黑樹閃亮登場, 紅黑樹就是一種平衡二叉樹。
紅黑樹是一種平衡二叉樹,遵照以下規則來保證紅黑樹的平衡,保證每一個節點在它左邊的後代數目和在它右邊的後代數目應該是大體相等(最長路徑也不會超過最短路徑的2倍)。
紅黑樹是在二叉查找樹基礎之上再遵循以下規則的樹
- 每一個節點顏色不是黑色就是紅色
- 根節點必定爲黑色
- 兩個紅色節點不能相鄰(紅色節點的子節點必定是黑色)
- 從任意節點到葉子節點的每條路徑包含的黑色節點數目相同(黑色高度)
- 每一個葉子節點(NULL節點,空節點)是黑色
當插入或刪除節點時,必需要遵照紅黑樹的規則,根據這些規則來決定是否須要改變樹的結構或節點顏色,使其達到平衡。
查找節點並不影響樹的平衡,因此紅黑樹的節點查找和二叉查找樹的操做是同樣的(請參考二叉查找樹)。
如圖: 紅黑樹 - 依次插入節點[100,200,300,400,500,600,700,800]
最終樹的結構是大體平衡的,不像二叉查找樹那樣偏向一側。
若是新插入元素或刪除元素後,紅黑樹的規則被破壞,這時須要對樹進行調整來從新知足紅黑樹規則。調整有變色和旋轉(左旋或右旋)兩種方式,接下來分別瞭解這兩種方式:
經過改變節點顏色修正紅黑樹,節點由紅變黑或黑變紅
經過改變節點的位置關係修正紅黑樹
如圖: 以右旋爲例
左旋則與右旋對稱,爲逆時針旋轉。
圖中空節點位置能夠是多個節點構成的子樹,也能夠是一個具體節點。
右旋(來源Java TreeMap):
private void rotateRight(Entry<K,V> p) { if (p != null) { Entry<K,V> l = p.left; p.left = l.right; if (l.right != null) l.right.parent = p; l.parent = p.parent; if (p.parent == null) root = l; else if (p.parent.right == p) p.parent.right = l; else p.parent.left = l; l.right = p; p.parent = l; } }
左旋(來源Java TreeMap):
private void rotateLeft(Entry<K,V> p) { if (p != null) { Entry<K,V> r = p.right; p.right = r.left; if (r.left != null) r.left.parent = p; r.parent = p.parent; if (p.parent == null) root = r; else if (p.parent.left == p) p.parent.left = r; else p.parent.right = r; r.left = p; p.parent = r; } }
紅黑樹的插入和刪除節點請看下一篇: 數據結構之紅黑樹-動圖演示(下) - 更新中 ...