數據結構(四)--- 紅黑樹(RedBlock-Tree)
文章圖片來自鄧俊輝老師課件
先提幾個問題去思考學習本文 :
- 紅黑樹和2-4樹(B-Tree)很像,那麽它存在的動機又是什麽呢
- 插入和刪除操作的邏輯又是怎麽樣的,時間和空間復雜度可以達到怎麽樣
- 和 AVL 對象有什麽區別呢
概述
定義
我們可以看到紅黑樹有4條重要的定義,這4條定義保證了這個平衡樹。下面我們看一下它和B-Tree的聯系。
從這個結構上說,我們可以知道B-Tree相比於紅黑樹,紅黑樹需要維護一個顏色這樣的屬性,需要空間,而同時紅黑樹搜索時可以
像二叉樹一般查找,而B-Tree每一個超級節點需要維護多個關鍵碼。這方面查看 RST_WIKI 這裏的分析。
但是這樣的樹是BBST嗎?
上面的數學推算已經向我證明,平均的深度為 h = O(LogN)
動機
Persistant Structure 一致性
下面介紹了紅黑樹為什麽適應一致性結構。
時間和空間復雜度紅黑樹可以適應條件。同時拓撲結構上,無論是插入還是刪除,都可以不超過O(1).
最壞情況下保證插入和刪除,查找
這裏引用 wiki 上的一段話說明紅黑樹在這方面的表現。
Red–black trees offer worst-case guarantees for insertion time, deletion time, and search time. Not only does this make them valuable in time-sensitive applications such as real-time applications, but it makes them valuable building blocks in other data structures which provide worst-case guarantees; for example, many data structures used in computational geometry can be based on red–black trees, and the Completely Fair Scheduler used in current Linux kernels and epoll system call implementation[19]
uses red–black trees.
時間空間復雜度
出處見參考資料
插入原理解析
我們定義插入的節點為紅色,那麽就有一種情況,雙紅,這違反了我們前面紅黑樹的定義,下面介紹如何解決雙紅問題。
RR-1
第一種情況 u 節點(uncle節點)是黑色的。
我們可以使用3+4實現,讓樹消除雙紅現象,3+4實現可以參考這篇文章 : 3+4
RR-2
u節點是紅色的情況下,最終可能導致樹高度+1
插入歸納
刪除原理解析
我們思考一下,假如先不看顏色,那麽二叉樹的刪除算法中,考察三種情況
- 擁有其中一個子節點
- 無子節點
- 擁有左右子節點
其中第一和第二種情況很好處理,直接刪除即可,有其中一個子節點需要重新連接一下父節點。第三種需要找出繼承節點,然後替
換掉刪除節點。繼承節點簡單點解釋就是右樹中最小的一個。
好了,那麽此時我們再來思考一下顏色的問題,我們知道紅黑樹不能紅紅相聯,且每個底部節點到根節點黑節點的數量都相等,要
是刪除節點是上面第一種情況和第二種情況且刪除節點是紅節點,直接刪除對樹的平衡沒有情況影響。
那麽要是刪除節點和繼承節點一黑一白呢,我們只需將刪除後,將繼承節點染黑就可以了,見下圖。
那麽刪除節點和繼承節點都是黑的情況呢?
雙黑缺陷
可以看到,當刪除節點和替代節點都為黑節點,刪除會產生下溢(下溢的概念可以參考上篇B-樹),需要考察繼承節點的父節點p和
兄弟節點 s ,下面分四種情況處理 :
BB-1
a’ 和 b’ 都是B-Tree 的撲拓結構,可以看到當s擁有一個紅節點時,產生下溢的節點通過旋轉,向兄弟節點借來了一個節點,從而
達到了平衡。而從 a 到 b 的過程,需要借助的是3+4操作。
BB-2R
BB-2R的情況,就相當於B-Tree的合並,而我們看到最終的撲拓結構是不變的,只需要進行染色,那麽當父節點被拿走了一個,是否會產生下溢呢?不會,因為在父節點中有紅節點,那麽左或右必有黑節點。
BB-2B
同樣也是合並操作,此時不是像BB-2R一樣是紅色節點了,那麽就有可能引發下沈下溢,那麽是不是會像AVL一樣進行LogN次的旋轉操作呢?不會,從a到b,我們可以他們的撲拓結構沒有改變,改變的只是顏色,所以不會發生LogN次的旋轉。時間復雜度依舊是O(1).
BB-3
可以看到,經過了左旋或是右旋,還有變色,由a到b後,黑高度依舊異常,可以有一個好消息就是,經過旋轉變成了我們之前處理
的情況一樣,即 BB-1 或是 BB-2R ,不會是 BB-2B的原因是 x 有個新的兄弟節點 s’ ,而且 p 為紅節點。
至此,我們雙黑的情況全部介紹完畢。
歸納總結和AVL的對比
代碼實現
代碼實現我們以java中的TreeMap 來解釋。本文只會介紹刪除操作。
/** * Removes the mapping for this key from this TreeMap if present. * * @param key key for which mapping should be removed * @return the previous value associated with {@code key}, or * {@code null} if there was no mapping for {@code key}. * (A {@code null} return can also indicate that the map * previously associated {@code null} with {@code key}.) * @throws ClassCastException if the specified key cannot be compared * with the keys currently in the map * @throws NullPointerException if the specified key is null * and this map uses natural ordering, or its comparator * does not permit null keys */ public V remove(Object key) { Entry<K,V> p = getEntry(key); if (p == null) return null; V oldValue = p.value; deleteEntry(p); return oldValue; } /** * Returns this map‘s entry for the given key, or {@code null} if the map * does not contain an entry for the key. * * @return this map‘s entry for the given key, or {@code null} if the map * does not contain an entry for the key * @throws ClassCastException if the specified key cannot be compared * with the keys currently in the map * @throws NullPointerException if the specified key is null * and this map uses natural ordering, or its comparator * does not permit null keys */ final Entry<K,V> getEntry(Object key) { // Offload comparator-based version for sake of performance if (comparator != null) return getEntryUsingComparator(key); if (key == null) throw new NullPointerException(); @SuppressWarnings("unchecked") Comparable<? super K> k = (Comparable<? super K>) key; Entry<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; } /** * Delete node p, and then rebalance the tree. */ private void deleteEntry(Entry<K,V> p) { modCount++; size--; // If strictly internal, copy successor‘s element to p and then make p // point to successor. if (p.left != null && p.right != null) { Entry<K,V> s = successor(p); p.key = s.key; p.value = s.value; p = s; } // p has 2 children //到了這裏,無論p是有幾個孩子,p這個變量變成了要刪除的節點 //要是 p有兩個child,會進入上面那個if,p變為了繼承節點 // Start fixup at replacement node, if it exists. Entry<K,V> replacement = (p.left != null ? p.left : p.right); if (replacement != null) { //只存在一個子節點 // Link replacement to parent 重新連接父節點,需要刪除的節點置為 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; // Null out links so they are OK to use by fixAfterDeletion. p.left = p.right = p.parent = null; // Fix replacement 開始修復,判斷是不是黑節點是因為紅節點直接刪除沒有影響 : // 每個底部節點到根節點的黑色節點數量相等 if (p.color == BLACK) fixAfterDeletion(replacement); } else if (p.parent == null) { // return if we are the only node. 該樹只有一個節點 root = null; } else { // No children. Use self as phantom replacement and unlink. 沒有子節點 if (p.color == BLACK) //繼承節點為黑 fixAfterDeletion(p); if (p.parent != null) { //繼承節點為紅,直接刪除 if (p == p.parent.left) p.parent.left = null; else if (p == p.parent.right) p.parent.right = null; p.parent = null; } } } // 實際就是解決雙黑節點的問題 /** From CLR */ private void fixAfterDeletion(Entry<K,V> x) { while (x != root && colorOf(x) == BLACK) { //非根且為黑節點 if (x == leftOf(parentOf(x))) { Entry<K,V> sib = rightOf(parentOf(x)); //取右兄弟節點 if (colorOf(sib) == RED) { setColor(sib, BLACK); setColor(parentOf(x), RED); rotateLeft(parentOf(x)); sib = rightOf(parentOf(x)); } if (colorOf(leftOf(sib)) == BLACK && colorOf(rightOf(sib)) == BLACK) { setColor(sib, RED); x = parentOf(x); } else { if (colorOf(rightOf(sib)) == BLACK) { setColor(leftOf(sib), BLACK); setColor(sib, RED); rotateRight(sib); sib = rightOf(parentOf(x)); } setColor(sib, colorOf(parentOf(x))); setColor(parentOf(x), BLACK); setColor(rightOf(sib), BLACK); rotateLeft(parentOf(x)); x = root; } } else { // symmetric //和前面是對稱的 Entry<K,V> sib = leftOf(parentOf(x)); // x是左節點 if (colorOf(sib) == RED) { //假如是BB-3,旋轉後只能是 BB-1 或者是 BB-2R setColor(sib, BLACK); setColor(parentOf(x), RED); rotateRight(parentOf(x)); sib = leftOf(parentOf(x)); } if (colorOf(rightOf(sib)) == BLACK && //BB-2R 或是 BB-2B colorOf(leftOf(sib)) == BLACK) { setColor(sib, RED); x = parentOf(x); } else { if (colorOf(leftOf(sib)) == BLACK) { //BB-1 中 紅在右邊 setColor(rightOf(sib), BLACK); setColor(sib, RED); rotateLeft(sib); sib = leftOf(parentOf(x)); } setColor(sib, colorOf(parentOf(x))); setColor(parentOf(x), BLACK); setColor(leftOf(sib), BLACK); rotateRight(parentOf(x)); x = root; //跳出while } } } setColor(x, BLACK); }
參考資料
- 鄧俊輝老師數據結構課程
- RST_WIKI
數據結構(四)--- 紅黑樹(RedBlock-Tree)