真c++ 从二叉树到红黑树(6)之红黑树RedBlack
??此文章為從二叉樹到紅黑樹系列文章的第六節,主要介紹介紹紅黑樹,相信,有了之前BST,AVL和B樹的鋪墊,你會很快地理解紅黑樹。但紅黑樹的情況也十分復雜,因此,推薦分兩天來看紅黑樹。一天看插入,一天看刪除。
文章目錄
- 一、所有文章鏈接~(點擊右邊波浪線可以返回目錄)
- 理解紅黑樹之前,需要了解的知識點:~
- 二、引入紅黑樹~
- 三、紅黑樹的性質~
- 1.紅黑樹的外部節點~
- 2.紅黑樹的性質~
- 紅黑樹性質解讀~
- 3.紅黑樹的適度平衡~
- 4.紅黑樹與B樹(2,4)樹的關系(提升變換)~
- 提升變換的四種組合~
- 四、紅黑樹類~
- (一)定義變量和接口~
- 1.利用已有的變量~
- 2.需要的接口~
- 3.重要輔助函數~
- (1)重寫高度更新算法~
- (2)雙紅,雙黑缺陷~
- 4.類內輔助靜態函數~
- 5.RedBlack.h~
- (二)高度更新~
- 高度更新代碼~
- (三)紅黑樹的插入代碼~
- (四)雙紅修復~
- 1) u為黑色~
- a) LL型~
- b) RR型~
- c) LR型~
- d) RL型~
- 2) u為紅色~
- a) LL型~
- b) RR型~
- c) LR型~
- d) RL型~
- 3) 求當前節點的叔叔代碼~
- 4) 雙紅修復代碼遞歸版~
- 5) 雙紅修復代碼迭代版~
- 6) 雙紅修復的復雜度~
- (五)紅黑樹的刪除~
- 1.再探removeAt語義~
- (1)removeAt的第一種和第二種情況~
- (2)removeAt的第三種情況~
- (3)結論~
- (4)removeAt其他細節~
- 2.紅黑樹的刪除~
- (1)刪除情況分析~
- (2)刪除代碼~
- (3)判斷黑高度是否平衡代碼~
- (六)雙黑缺陷~
- (1)s為黑,其至少有一個紅孩子~
- (2)s為黑,s的兩個孩子為黑,p為紅~
- (3)s為黑,s的兩個孩子為黑,p為黑~
- (4)s為紅,s的兩個孩子必然為黑,p必然為黑~
- (5)雙黑修復遞歸版~
- (6)雙黑修復迭代版~
- (7)雙黑修復復雜度~
- 五、完整RedBlack.h~
- 六、紅黑樹測試~
- 1.插入測試代碼~
- 2.插入測試圖示~
- 3.刪除測試代碼~
- 4.刪除測試圖示~
- 七、結語~
一、所有文章鏈接~(點擊右邊波浪線可以返回目錄)
??在閱讀本文前,強烈建議你看下前面的文章的目錄、前言以及基本介紹,否則你無法理解后面的內容。鏈接如下:
理解紅黑樹之前,需要了解的知識點:~
二、引入紅黑樹~
??AVL樹盡管可以保證最壞情況下的單次操作速度,但需在節點中嵌入平衡因子等標識;更重要的是,刪除操作之后的重平衡可能需做多達?log2n?次旋轉,從而頻繁地導致全樹整體拓撲結構的大幅度變化。
??紅黑樹即是針對后一不足的改進。通過為節點指定顏色,并巧妙地動態調整,紅黑樹可保證:在每次插入或刪除操作之后的重平衡過程中,全樹拓撲結構的更新僅涉及常數個節點。
三、紅黑樹的性質~
1.紅黑樹的外部節點~
??一棵樹,所有葉節點都有空孩子指針,因此,為了方便理解,可以將這些空孩子指針全部視為外部節點。即假想地加入外部節點(實際并沒有加入),使得樹中任何節點都可以視為有左右孩子。
??下面是一顆紅黑樹
??若按1中給這顆樹增加外部節點,就可以得到
2.紅黑樹的性質~
??提示:如果你看紅黑樹的算法,感到某些地方不好理解時,不妨來看看紅黑樹的性質,你就會明白算法為什么要這么設計。
由紅、黑兩色節點組成的二叉搜索樹若滿足以下條件,即為紅黑樹
(1) 樹根始終為黑色
(2) 外部節點均為黑色(NULL LEAF)(假想,實際不存在)
(3) 其余節點若為紅色,則其孩子節點必為黑色,反之,其父親也必然為黑色。
(4) 從根節點到任一外部節點的沿途,黑節點的數目相等(黑深度相等)
紅黑樹性質解讀~
3.紅黑樹的適度平衡~
??由2中的紅黑樹的性質解讀的第三條,可以得知
在從根節點通往任一節點的沿途,黑節點都不少于紅節點。??而一棵樹,就是由紅節點和黑節點組成,這樣就代表,黑節點的數目,至少比全樹所有節點的數目的一半大。而這一點,恰恰就是紅黑樹適度平衡的條件。
TB為黑高度(H),T為全樹的高度(h)。 更嚴格的有log2(n + 1) <= h <=2?log2(n + 1)(證明略)??盡管紅黑樹不能如完全樹那樣可做到理想平衡,也不如AVL樹那樣可做到較嚴格的適度平衡,但其高度仍控制在最小高度的兩倍以內,從漸進的角度看仍是O(logn),依然保證了適度平衡—這正是紅黑樹可高效率支持各種操作的基礎。
4.紅黑樹與B樹(2,4)樹的關系(提升變換)~
??往下看之前,建議你理解一下B樹的上溢和下溢。不懂的就看看本系列文章的第五部分,我對B樹進行了詳解。
??在后面就可以得知,經適當轉換之后,紅黑樹和(2,4)樹相互等價!
??具體地,自頂而下逐層考查紅黑樹各節點。每遇到一個紅節點,都將對應的子樹整體提升一層,從而與其父節點(必黑)水平對齊,二者之間的聯邊則相應地調整為橫向。
???????????????
??由紅黑樹的性質(3)可得,對于有紅孩子的黑節點而言,提升過程中,所涉及的節點至多不超過3個(可能為2個,當只有一個紅孩子時),因為其最多只有兩個紅孩子,而對應的紅孩子必然只有黑孫子,沒有紅孫子。
??因此由變換之后的結果可以觀察到,可以把變換之后的3個節點(或2個節點)看做一個整體,其恰好可以構成4階B樹(3個關鍵碼)中的一個節點。因此,變換之后,每一顆紅黑樹都對應一顆(2,4)樹。
提升變換的四種組合~
1、 通往黑節點的邊對紅黑樹的黑高度有貢獻,以實線表示,保留下來。
2、 通往紅節點的邊對紅黑樹的黑高度沒有貢獻,以虛線表示,不予保留。
??從上圖可以看出,對應的(2,4)B樹。每個節點有且僅有一個黑色的關鍵碼,同時紅色的關鍵碼不超過兩個,若某個節點果真包含兩個紅關鍵碼,則黑關鍵碼的位置必然居中。
四、紅黑樹類~
(一)定義變量和接口~
1.利用已有的變量~
在第一部分定義二叉樹節點的時候,我們定義了一個
RBColor _color;//紅黑樹專用這個枚舉類,主要是用于表示紅黑樹的顏色信息。具體為
namespace {enum class RBColor { RED, BLACK }; }并且同樣,我們會用到在BST定義的_hot節點
BinNodePtr _hot;//"命中節點"的"父親"2.需要的接口~
??由于在BST中,我們已經定義了查找search算法,因此,不需要給RedBlack重新寫查找算法,只需要對插入和刪除算法進行重寫既可(并且在后面可以發現,其插入和刪除的本質,跟BST和AVL一模一樣!)。并且在BinTree中,我們也定義了遍歷算法,因此,也沿用即可。
在樹中插入一個節點insert 在樹中刪除一個節點remove3.重要輔助函數~
(1)重寫高度更新算法~
??由于紅黑樹的高度的表示方式為黑高度,所以其高度更新的算法也需要進行重寫
更新高度updateHeight(2)雙紅,雙黑缺陷~
??這兩個輔助函數,正是紅黑樹得以保持平衡的最主要原因。在接下來介紹插入時,會解釋雙紅缺陷,在介紹刪除時,會解釋雙黑缺陷。
solveDoubleRed解決雙紅缺陷 solveDoubleBlack解決雙黑缺陷4.類內輔助靜態函數~
??為了加快算法執行的效率,和方便理解,在紅黑樹類內定義了4個靜態內聯函數。前兩個很好理解,后面兩個在介紹插入和刪除算法時會進行解釋。
IsBlack//判黑//當然x為空,也為黑色 IsRed//非黑即紅IsBlackHeightBalanced//判斷是否需要更新黑高度 uncle//獲取當前節點的叔叔5.RedBlack.h~
template<typename T=int> class RedBlack :public BST<T> { protected:using BinNodePtr = BinNode<T>*;protected:void solveDoubleRed(BinNode<T>* x);//雙紅修正void solveDoubleBlack(BinNode<T>* replacer);//雙黑修正constexpr int updateHeight(BinNode<T>* x)const override;//更新高度public:BinNode<T>* insert(const T& data)override;//插入重寫bool remove(const T& data)override;//刪除重寫/*查找沿用BST的查找*//*遍歷沿用BinTree的遍歷*/protected:static constexpr bool IsBlack(const BinNodePtr& x) {//判黑//當然x為空,也為黑色return ((!x) || (RBColor::BLACK == x->_color));}static constexpr bool IsRed(const BinNodePtr& x) {//非黑即紅return !IsBlack(x);}static constexpr bool IsBlackHeightBalanced(const BinNodePtr& x) {//判斷是否需要更新黑高度bool is_L_C_Equal = (stature(x->_lchild) == stature(x->_rchild));int rbHeight = (IsRed(x) ? stature(x->_lchild) : stature(x->_lchild) + 1);//對于rbHeight的計算而言,取左孩子還是右孩子,均一樣bool is_Height_Equal = (x->_height == rbHeight);return is_L_C_Equal && is_Height_Equal;//只要有一個為假,即為假//所以只要左孩子和右孩子高度相等,或者x沒有高度變化,就黑高度平衡 }static inline BinNodePtr uncle(const BinNodePtr& x) {/*獲取x的叔叔*/return IsLChild(x->_parent) ? x->_parent->_parent->_rchild : x->_parent->_parent->_lchild;}};//class RedBlack(二)高度更新~
??下面是我們在本系列文章第一部分定義的獲取當前節點高度的全局靜態函數。并且我們規定當沒有節點時,高度為-1,當有一個節點時,高度為0(見第一部分關于樹的語義規定中樹的高度的定義)。此規定依然適用于紅黑樹,也就是說,哪怕紅黑樹此時只有一個根節點(必然為黑),其高度為0而不是1。
??此規定,對于后序紅黑樹的平衡不造成任何影響,但若讀者要獲取紅黑樹的高度的話,就需要明白此時的黑高度,比實際的黑高度少1。
高度更新代碼~
template<typename T> constexpr int RedBlack<T>::updateHeight(BinNode<T>* x) const//由于stature視空節點高度為-1,所以height會比黑高度少一 {x->_height = std::max(stature(x->_lchild), stature(x->_rchild));//孩子一般黑高度相等,除非出現雙黑return IsBlack(x) ? x->_height++ : x->_height;//若當前節點為黑,則計入黑高度 }由于重寫了更新高度函數,所以此時x的高度,為黑高度。
要更新紅黑樹的高度(即黑高度),只有當:
(三)紅黑樹的插入代碼~
??紅黑樹的插入算法,與BST,AVL的基本插入方式一模一樣,唯一不同的是后續要進行雙紅修復。
??先用BST的search確定不存在這個節點,并且更新_hot的位置,并以_hot為父親,創建一個新節點。并將其黑高度更新為-1,以及染色成紅色(我們默認新加入的節點均為紅色節點,除非新加的是根節點)。
??由BinNode節點的構造函數,默認新節點為紅色。
template<typename T> BinNode<T>* RedBlack<T>::insert(const T& data) {BinNode<T>*& x = this->search(data);//沿用BST的查找//并更新_hot//用引用接收if (x)//如果節點存在,則返回return x;x = new BinNode<T>(data, this->_hot, nullptr, nullptr, -1);//設定黑高度-1,并默認節點為紅色this->_size++;solveDoubleRed(x);//雙紅修正//x此時必為紅return x; }(四)雙紅修復~
??因新節點的引入,而導致父子節點同為紅色的此類情況,稱作“雙紅”(double red)。每引入一個關鍵碼,雙紅修正函數都可能迭代地調用多次。在此過程中,當前節點x的兄弟及兩個孩子(初始時都是外部節點),必然均為黑色。
因為x的父親為紅色,所以其只可能有黑孩子,所以x若有兄弟,則必為黑色。 由于x為新節點,其外部節點為空,即默認均為黑孩子。??將x的父親與祖父分別記作p和g。既然此前的紅黑樹合法,故作為紅節點p的父親,g必然存在且為黑色。
此時的g,必然存在,否則作為樹根的節點p不可能為紅色;并且g作為紅色節點p的父親,其必然為黑色的在下面的過程中,僅僅有x p g這三個節點還不夠,因此,還需要一個額外的節點,即p的兄弟(x的叔叔)u。
??以下,視節點u的顏色不同(若u不存在,其顏色也視為黑,這符合之前外部節點的顏色定義),分兩類情況分別處置。
1) u為黑色~
??u為黑色時,具體來說,對應四種結構.
??此時x的兄弟和兩個孩子的黑高度必然都與u的黑高度相等。
a) LL型~
在原來的樹中,插入了新節點x。構成下圖所示的結構。
此時,可以利用B樹來理解,不妨先將紅黑樹,經過提升變換,提升為對應的(2,4)B樹。
從B樹的結構可以看出,其不滿足先前提升變換的四種情況中的任何一種。
因此,要想其滿足提升變換的四種情況,最簡單的做法,就是將p與g互換顏色,讓對應的(2,4)B樹變成下圖所示形式
再將對應的(2,4)B樹還原成紅黑樹,即為
即原來的 x 變成 a,原來的 p 變成 b ,原來的 g 變成 c 。
但也注意到,相應的孩子節點的位置也發生了新的變化。
??因此,如何做到這樣的變化呢?此時不妨想想在本系列文章第三部分定義的AVL中的connect34算法,其對應的形狀也是這樣的形狀
??沒錯,只要我們將對應的x p g按照connect34的形式進行重構,就可以達到目的。
b) RR型~
??同LL型一樣處理,不多贅述。
c) LR型~
??在原來的樹中,插入了新節點x。構成下圖所示的結構。此時仍然滿足x的兄弟和兩個孩子的黑高度必然都與u的黑高度相等這個條件。
此時,同樣可以利用B樹來理解,不妨先將紅黑樹,經過提升變換,提升為對應的(2,4)B樹。
??情況總是驚人的相似,可以發現,現在的形狀的調整方式,不正是同LL型的調整方式一模一樣么?只是x p g的相對位置有所變化。因此,也是需要進行connect34重構。
d) RL型~
??同LR型一樣處理,不多贅述。
2) u為紅色~
??u為紅色時,具體來說,對應四種結構
??此時,u的左、右孩子均為黑色(可能為空),其黑高度必與x的兄弟以及兩個孩子相等。
a) LL型~
在原來的樹中,插入了新節點x。構成下圖所示的結構。
此時,同樣可以利用B樹來理解,不妨先將紅黑樹,經過提升變換,提升為對應的(2,4)B樹。
在介紹LR型的時候,會展示怎么處理這種情況。
b) RR型~
??同LL型一樣處理,不多贅述。
c) LR型~
??在原來的樹中,插入了新節點x。構成下圖所示的結構。此時仍然滿足x的兄弟和兩個孩子的黑高度必然都與u的黑高度相等這個條件。
此時,同樣可以利用B樹來理解,不妨先將紅黑樹,經過提升變換,提升為對應的(2,4)B樹。
??可以看到,提升變換之后,其這個大節點的關鍵碼數目,均必然為4個,超出了4階B樹的個數限制(4階B樹的一個大節點的關鍵碼數最多只能有3個)。所以可以仿照B樹的情況,進行一次上溢。同時進行染色以滿足原來B樹提升變換后的四種形態。(問號節點中必然有一個為黑,按照紅黑樹的提升變換,只有當g染成紅時才可以進行提升變換)
從紅黑樹的角度來看,對比沒有變換之前
??從宏觀上來看,對于紅黑樹而言,只需要將p u的顏色由紅色變成黑色,并且若g不為根節點的話,就將g染色成紅色。當然,若g此時就是根節點,其強制變成黑色。
??同樣,由于g變成了紅色,所以還需要繼續判斷g的父親是否是紅色,因此要繼續進行雙紅修復。最壞的情況,可能要持續到根節點。(由x到g,上升了兩層)。累計最多迭代logn次。
d) RL型~
??同LR型一樣處理,不多贅述。
3) 求當前節點的叔叔代碼~
static inline BinNodePtr uncle(const BinNodePtr& x) {/*獲取x的叔叔*/return IsLChild(x->_parent) ? x->_parent->_parent->_rchild : x->_parent->_parent->_lchild; }4) 雙紅修復代碼遞歸版~
??注意要是已經遞歸到樹根,則樹根強制轉黑
template<typename T>void RedBlack<T>::solveDoubleRed(BinNode<T>* x){if (IsRoot(x)) {//若已遞歸到樹根,則樹根轉黑,整樹高度也隨之遞增this->_root->_color = RBColor::BLACK;this->_root->_height++;return;}//否則x的父親必然存在BinNode<T>* p = x->_parent;//x的父親if (IsBlack(p))//如果x的父親為黑,則終止調整return;//否則x的父親必然為紅,則BinNode<T>* g = p->_parent;//x的祖父必然存在,并且,其顏色必然為黑色BinNode<T>* u = uncle(x);//x的叔叔,可能為空節點if (IsBlack(u)) {//當u為黑色時(u為空時,也為黑色)if (IsLChild(x) == IsLChild(p))p->_color = RBColor::BLACK;elsex->_color = RBColor::BLACK;g->_color = RBColor::RED;/// 以上雖保證總共兩次染色,但因增加了判斷而得不償失/// 在旋轉后將根置黑、孩子置紅,雖需三次染色但效率更高BinNode<T>*& newNode = this->FromParentTo(g);//先記錄祖父的父親的孩子指針newNode = this->rotateAt(x);/*對x進行調整,調整之后的返回值必然為調整之后的局部子樹的樹根位置,此時,只需要將此樹根作為原來祖父的父親的孩子既可,當然,高度也隨之更新*/return;//只要旋轉了一次,就調整完整,退出循環}else {//若u為紅色p->_color = RBColor::BLACK;//父親此時必然為紅色,將其轉黑p->_height++;u->_color = RBColor::BLACK;//叔叔此時必然為紅色,將其轉黑u->_height++;if (!IsRoot(g))//如果祖父不為根節點,就轉紅g->_color = RBColor::RED;solveDoubleRed(g);//遞歸用}}5) 雙紅修復代碼迭代版~
??為了方便理解,我將遞歸的部分變成了注釋,并未進行刪除,方便讀者比對,迭代版中,不僅將尾遞歸轉換成了迭代,也將染色的效率進行了優化。
template<typename T> void RedBlack<T>::solveDoubleRed(BinNode<T>* x) {while (true) {if (IsRoot(x)) {//若已迭代到樹根,則樹根轉黑,整樹高度也隨之遞增this->_root->_color = RBColor::BLACK;this->_root->_height++;return;}//否則x的父親必然存在BinNode<T>* p = x->_parent;//x的父親if (IsBlack(p))//如果x的父親為黑,則終止調整return;//否則x的父親必然為紅,則BinNode<T>* g = p->_parent;//x的祖父必然存在,并且,其顏色必然為黑色BinNode<T>* u = uncle(x);//x的叔叔,可能為空節點if (IsBlack(u)) {//當u為黑色時(u為空時,也為黑色)//if (IsLChild(x) == IsLChild(p))// p->_color = RBColor::BLACK;//else// x->_color = RBColor::BLACK;//g->_color = RBColor::RED;/// 以上雖保證總共兩次染色,但因增加了判斷而得不償失/// 在旋轉后將根置黑、孩子置紅,雖需三次染色但效率更高BinNode<T>*& newNode = this->FromParentTo(g);//先記錄祖父的父親的孩子指針newNode = this->rotateAt(x);/*對x進行調整,調整之后的返回值必然為調整之后的局部子樹的樹根位置,此時,只需要將此樹根作為原來祖父的父親的孩子既可,當然,高度也隨之更新*///重染色/*能省去之前的判斷*/newNode->_color = RBColor::BLACK;newNode->_lchild->_color = RBColor::RED;newNode->_rchild->_color = RBColor::RED;return;//只要旋轉了一次,就調整完整,退出循環}else {//若u為紅色p->_color = RBColor::BLACK;//父親此時必然為紅色,將其轉黑p->_height++;u->_color = RBColor::BLACK;//叔叔此時必然為紅色,將其轉黑u->_height++;if (!IsRoot(g))//如果祖父不為根節點,就轉紅g->_color = RBColor::RED;//solveDoubleRed(g);//遞歸用x = g;//將x變成其祖父,進入迭代循環。}} }6) 雙紅修復的復雜度~
??鄧老師已經用一個流程圖和一個表格,幫助我們詳細地分析了雙紅修復算法的復雜度。其中zag,zig是左右旋(也就是我們的connect34重構)
??可見,對于u為黑的情況,只需做一輪修正;u為紅的情況雖有可能需要反復修正,但由于修正位置的高度會嚴格單調上升,故總共也不過O(logn)輪。另外從該表也可看出,每一輪修正只涉及到常數次的節點旋轉或染色操作。
??因此,節點插入之后的雙紅修正,累計耗時不會超過O(logn)。即便計入此前的關鍵碼查找以及節點接入等操作,紅黑樹的每次節點插入操作,都可在O(logn)時間內完成。
(五)紅黑樹的刪除~
??紅黑樹的刪除算法,本質同BST,AVL的刪除。在刪除節點方面。兩者沒什么區別,唯一的區別即高度的更新方式不同。因此,可以調用在BST中設定的給AVL的刪除接口removeAt全局函數。
現在我們繼續來看看removeAt函數的作用
定義在BST中的removeAt函數
template<typename T>//適用于AVL Splay,RedBlack等,必須這么設計,才能做到完美刪除,且保持BST的性質 static BinNode<T>* removeAt(BinNode<T>*& x, BinNode<T>*& hot) {//這里x必須用引用,才不會使指針亂指using BinNodePtr = BinNode<T>*; //記錄x的地址里面保存的值,若刪除temp里面的值,即刪除x里面的值,但x的本身地址不會影響temp,反之亦然。BinNodePtr temp = x;//替代被刪除節點的接替者,一般為被刪除節點的左孩子或者右孩子,而不是x的左孩子或者右孩子BinNodePtr replacer = nullptr;if (!HasLChild(x)) {//如果x沒有左孩子,或者x左右孩子均無,則將x的右孩子作為x,并將接替者設為x的右孩子x = x->_rchild;replacer = x;}else if (!HasRChild(x)) {//如果x沒有右孩子,則將x的左孩子作為x,并將接替者設為x的左孩子x = x->_lchild;replacer = x;}else {temp = temp->succ();//取得中序遍歷的后繼//這個后繼必將沒有左孩子std::swap(x->_data, temp->_data);//交換對應的值if (temp->_parent == x) {//如果后繼的父親是原來的x,后繼必然為x的右孩子replacer = temp->_rchild; //就將后繼的右孩子作為父親的右孩子temp->_parent->_rchild = replacer;}else {//如果后繼的父親不是原來的x,后繼必然為某一節點的左孩子replacer = temp->_rchild;temp->_parent->_lchild=replacer;//就將后繼的右孩子作為這個節點左孩子}}//hot即被刪除節點的父親。而temp正是要刪除的節點。hot = temp->_parent;if (replacer)//若replacer存在,則必須將其父指針指向hot。不然如同x->_rchild的父親指向的還是原來的replacer->_parent = hot;//釋放原來x所指的堆區的數據,或者x的后繼的堆區的數據release(temp->_data);release(temp);return replacer; }1.再探removeAt語義~
??從removeAt的語義(3種刪除的情況)來看,刪除的節點可能是x,可能是x的后繼。
(1)removeAt的第一種和第二種情況~
??從removeAt的第一種和第二種情況來看,刪除的節點必然為x。即表明此時的x要么沒有孩子,要么只有一個孩子。即此時x必然有一個空孩子(也一定是黑孩子)。
下面圖的情況,全部是用來刪除節點x ?? 刪除節點1,實際刪除的也是1(removeAt的第一種情況) 刪除節點1,實際刪除的也是1(removeAt的第一種情況) 刪除節點1,實際刪除的也是1(removeAt的第二種情況)
??可以看到,此時x必然有一個空孩子(也一定是黑孩子)。
(2)removeAt的第三種情況~
??從removeAt的第三種情況來看,刪除的節點為x的后繼,由后繼的定義可知,這個后繼必然沒有左孩子(這種后繼的情況,必然屬于在本系列文章第一節談到的求后繼的第一種情況,并且我用紅字標明了這種后繼一定沒有左孩子),不然不可能刪這個后繼。
刪除節點10,實際刪除的是15(removeAt的第三種情況)
??可以看到,15必然沒有左孩子,其左孩子為空節點(黑節點)。
因此,x的后繼必然有一個空孩子(也是黑孩子)。
(3)結論~
綜合(1)(2)可以得到一個非常重要的結論:
無論被刪除的是x還是x的后繼,被刪除節點,都必然有一個空孩子(也是黑孩子)。
(4)removeAt其他細節~
??1. 在removeAt函數中,交換x與后繼的值時,也只是交換了它們的data值,并非交換了它們的所有東西,因此,x的原來的顏色,和其后繼的顏色均沒變。
std::swap(x->_data, temp->_data);//交換對應的值??2.在刪除完成后_hot節點也指向了被刪除節點的父親節點。
//hot即被刪除節點的父親。而temp正是要刪除的節點。 hot = temp->_parent;??3.removeAt的返回值是replacer,即被刪除節點的接替者。
BinNodePtr replacer//替代被刪除節點的接替者,一般為被刪除節點的左孩子或者右孩子,可能為空2.紅黑樹的刪除~
(1)刪除情況分析~
??對于刪除時節點(可能是x被刪,也可能是其后繼被刪)的顏色進行分類討論的話,無外乎就五種情況。
(1)被刪除后,原樹沒有任何節點,刪除即可完成。
(2)被刪除的是根節點,只需要將其接替者replacer染成黑色,并更新高度既可,刪除完成。
(3)實際被刪除節點x為紅色,其必然只有黑孩子和黑父親,并且w必然為空(根據removeAt的結論),此時只需將r接替x的位置既可。(當然r也可能為空)
x代表被刪除節點,w代表必然為空的那個節點,r代表replacer也就是被刪除節點的接替者。p為x的父親
??此時,毫無例外,刪除x對原樹的黑高度肯定沒有影響,因此直接刪除既可。刪除完成后,即可結束。
(4)實際被刪除節點為黑色,w同樣也為空(根據removeAt的結論)。若r為紅色,此時,只需要在r接替x的位置之后,將r轉成黑色,那么原樹的黑高度也必然恢復,刪除完成。
(5)實際被刪除節點為黑色,w同樣也為空(根據removeAt的結論)。若r為黑,即出現雙黑情況,則需要做進一步的調整變換。(當然,這種情況,也必然包含了r為空孩子的情況)
(2)刪除代碼~
template<typename T> bool RedBlack<T>::remove(const T& data) {BinNode<T>*& x = this->search(data);//找有沒有這個節點,如果沒有,則返回false,記住用引用if (!x)return false;BinNode<T>* replacer = removeAt(x, this->_hot);//調用在BST定義的全局靜態函數removeAt,返回被刪除節點的接替者,同時更新_hot--this->_size;//更新規模//1.如果這個被刪除節點是樹中唯一節點,則直接返回if (this->_size==0) {this->_root = nullptr;//將根節點置空return true;}//2.如果被刪除節點為根節點,則_hot必然為空//但如果進行到此,說明此時_root必然不為空,不然上一就會退出if (this->_hot == nullptr) {this->_root->_color = RBColor::BLACK;//就將此時的根節點直接染成黑色updateHeight(this->_root);//并更新根節點的高度return true;}//如果進行到此,說明被刪除節點必然存在,并且不為根節點。_hot也必然存在/*3.如果_hot的黑高度不變則返回*//*此時也必然包括了被刪除節點為紅色節點的情況,若為紅色,則刪除對高度沒有影響*//*當然,也包括了雙黑的可能情況*/if (IsBlackHeightBalanced(this->_hot))return true;/*4.如果_hot的黑高度變了,說明被刪除節點必然為黑色*/if (IsRed(replacer)) {//就看x的接替者是不是紅色,如果是紅色,將其染成黑色,就必然可以使樹的高度恢復。replacer->_color = RBColor::BLACK;replacer->_height++;return true;}/*5.如果進行到此,就必然說明被刪除節點和replacer均為黑色節點(replacer可能為空),此時,就需要進行雙黑缺陷判斷*//*要進行到這里的條件即為被刪除節點的父親的黑高度變了,并且被刪除節點的接替者也為黑色時。*/solveDoubleBlack(replacer);return true; }(3)判斷黑高度是否平衡代碼~
在紅黑樹的刪除過程中,我們需要判斷黑高度是否平衡。
static constexpr bool IsBlackHeightBalanced(const BinNodePtr& x) {//判斷是否需要更新黑高度bool is_L_C_Equal = (stature(x->_lchild) == stature(x->_rchild));int rbHeight = (IsRed(x) ? stature(x->_lchild) : stature(x->_lchild) + 1);//對于rbHeight的計算而言,取左孩子還是右孩子,均一樣bool is_Height_Equal = (x->_height == rbHeight);return is_L_C_Equal && is_Height_Equal;//只要有一個為假,即為假//所以只要左孩子和右孩子高度相等,或者x沒有高度變化,就黑高度平衡 }(六)雙黑缺陷~
??若被刪除節點和其接替者(可能為空)均為黑。則顯然,為了保持紅黑樹的平衡性,原來被刪除節點必然有一個非空兄弟(其孩子可能均為空),不然黑高度就無法維持。
??不妨將被刪除節點x的兄弟記作s。被刪除節點的父親記作p。無論哪一個,顏色都不確定。
??因此,分s和p的顏色情況,分四種情況來討論。
(1)s為黑,其至少有一個紅孩子~
??先以s為p的左孩子,x為p的右孩子來處理(對稱情況處理方式完全相同)
??左為紅黑樹,右為對應的B樹
??此時刪除了節點x,類似于B樹的處理方式,由于其左兄弟有一個多余的關鍵碼,所以要從其左兄弟借一個關鍵碼。調整后為(當然,調整之后,要滿足提升變換的四種情況,因此需要重染色)
??并且圖(b)的結構也是令人十分熟悉,沒錯,也就是connect34重構。
??因此,從紅黑樹的角度來看,此過程等效于對節點t,s,p進行3+4重構。
??位置調整好后,再進行重染色,即把t,p染成黑色,s繼續沿用之前p的顏色。并且在此過程中,r的顏色沒有發生變化。
??顯然,調整完之后,紅黑樹的高度得以復原。因此調整完畢。
(2)s為黑,s的兩個孩子為黑,p為紅~
??先以s為p的左孩子,x為p的右孩子來處理(對稱情況處理方式完全相同)
??左為紅黑樹,右為對應的B樹
??此時的B樹,被刪除的x無法從s中借關鍵碼,所以只有父親下溢。為保持紅黑樹的性質不變,因此下溢后,只需將s和p的顏色互換,就能保持性質。
(p的左右節點中有且僅有一個黑色關鍵碼,因此p下溢,不會造成對應B樹結構的破壞) ????因此,從紅黑樹的角度來看,只需將s與p的顏色進行互換,就能使紅黑樹的高度得到復原。
(3)s為黑,s的兩個孩子為黑,p為黑~
??先以s為p的左孩子,x為p的右孩子來處理(對稱情況處理方式完全相同)
??左為紅黑樹,右為對應的B樹
??由于刪除了x,s也沒有足夠的關鍵碼,因此,只能p下溢。并且由于p所在層次,必然只有p一個關鍵碼,因此,p的下溢,必將導致上層下溢。
??下溢之后,將s置為紅色,對應的紅黑樹為
??因此,從紅黑樹的角度來看,即把s由黑轉紅。
??由于p是下溢過來的,所以需要做進一步的檢查,此時等效于原樹中p的黑父親剛刪除,因此可以看做又是一次雙黑缺陷。所以需要再次做迭代循環。
??這也是雙黑修正過程中,需要再次迭代的唯一可能。(可能進入情況1 2 3 4中任何一種)。
(4)s為紅,s的兩個孩子必然為黑,p必然為黑~
??先以s為p的左孩子,x為p的右孩子來處理(對稱情況處理方式完全相同)
??左為紅黑樹,右為對應的B樹
??從B樹的角度來看,此時可以將p下溢,并將s’轉紅,s轉黑,但這樣會導致以s’為根的子樹的高度變化,并且由于x被刪除,以x為根的子樹的高度必然也下降。所以,如果僅僅這么調整,會造成兩顆子樹的高度減少,使得情況變得更加復雜。
??而先輩們已經有了很好的解決方式。
??即先將s與p互換顏色,得到左圖所示的B樹,將其轉換為紅黑樹為右圖所示。
??從紅黑樹的角度來看,這一轉換對應于以節點p為軸做一次旋轉,并交換p與s的顏色。
??可以發現,經過上述處理后,雙黑缺陷依然存在,而且缺陷位置的高度也未上升。但此次變換并非沒有意義,仔細觀察圖b可以發現,被刪除節點x有了一個新兄弟s’,并且s’必然為黑。
??并且,調整之后,可以發現a與b所示的紅黑樹,完全等價。
??再仔細觀察,不難發現,此時x p s’對應的情況,不正是之前雙黑修復過程中出現的情況(1)與(2)么
??所以,只需要將調整之后的紅黑樹,再進行一次雙黑修復,就必然可以修復高度。
(5)雙黑修復遞歸版~
template<typename T> void RedBlack<T>::solveDoubleBlack(BinNode<T>* replacer) {BinNode<T>* p = replacer ? replacer->_parent : this->_hot;if (p == nullptr)return;//如果replacer的父親為空,則返回/*由下面的情況來分析,無論哪種情況,遞歸的這個節點,必然為黑色*/所以不需要考慮將根節點強轉黑色BinNode<T>* sibling = (replacer == p->_lchild) ? p->_rchild : p->_lchild;//原來x的兄弟,也就是replacer此時的兄弟if (IsBlack(sibling)) {BinNode<T>* s_Red_child = nullptr;//sibling的紅孩子(若左右孩子皆為紅,則左者優先;皆黑時為nullptr)if (IsRed(sibling->_rchild))//需要判斷sibling是不是空指針s_Red_child = sibling->_rchild;//右孩子if (IsRed(sibling->_lchild))s_Red_child = sibling->_lchild;//左孩子 /*1.第一種情況,黑s有紅孩子*/if (s_Red_child != nullptr) {//如果sibling有紅孩子RBColor oldColor = p->_color;//備份父親的顏色/*接下來對s的紅孩子,s以及p進行3+4重構*//*根據3+4重構后的定義,其返回的節點為根節點指針,這個根節點的名字不妨設為newNode*/BinNode<T>*& newNode = this->FromParentTo(p);//首先記錄父親的 父親的孩子的指針newNode = this->rotateAt(s_Red_child);//3+4重構//對3+4重構后的節點進行重染色if (HasLChild(newNode)) {newNode->_lchild->_color = RBColor::BLACK;updateHeight(newNode->_lchild);}if (HasRChild(newNode)) {newNode->_rchild->_color = RBColor::BLACK;updateHeight(newNode->_rchild);}newNode->_color = oldColor;//新子樹根節點繼承原根節點的顏色updateHeight(newNode);//更新高度/*至此,就調整完畢,紅黑樹恢復平衡*/}/*黑s沒有紅孩子,及其孩子均為黑色(可能為空),對其父親是否為紅色進行判斷*/else {sibling->_color = RBColor::RED;//無論父親是否為紅色,都需要把s設為紅色sibling->_height--; /*2.黑s只有黑孩子(可能為空,并且其父親為紅色*/if (IsRed(p)) {p->_color = RBColor::BLACK;//直接將父親設定為黑色,父親的高度必然沒有發生變化。//因為p原來為紅,現在由于兩個孩子高度都減了一,所以將其變成黑色后,其高度就恢復了原來的高度/*至此,就調整完畢,紅黑樹恢復平衡*/} /*3.黑s只有黑孩子(可能為空,并且其父親為黑色*/else {p->_height--;//相當于p的父親被刪,然后對p是父親的replacer,因此,對p進行遞歸既可。solveDoubleBlack(p);//用遞歸時用/*之后可能進入1 2 3 4四種情況中的任何一種,最壞可能到根*/}}} /*4.s為紅,此時其必然只有黑孩子(黑孩子可能均為空),當然s的父親p此時也必然為黑*/else {sibling->_color = RBColor::BLACK;//將s和p的顏色互換p->_color = RBColor::RED;//取與s同側的孩子BinNode<T>* s_child = IsLChild(sibling) ? sibling->_lchild : sibling->_rchild;this->_hot = p;//首先將p的父親記錄起來BinNode<T>*& newNode = this->FromParentTo(p);//首先記錄p的 父親的孩子的指針newNode = this->rotateAt(s_child);//將s_child,s與p 進行3+4重構/*調整之后,樹的局部結構就發生了變化*/solveDoubleBlack(replacer);//用遞歸時用//由第四種情況的分析,可知,只需要修復重構后的replacer就可以//并且之后只有可能進入第一種和第二種情況,必然不可能進入第三種情況} }(6)雙黑修復迭代版~
??為了方便理解,我將遞歸的部分變成了注釋,并未進行刪除,方便讀者比對。
template<typename T> void RedBlack<T>::solveDoubleBlack(BinNode<T>* replacer) {while (true) {BinNode<T>* p = replacer ? replacer->_parent : this->_hot;if (p == nullptr)return;//如果replacer的父親為空,則返回/*由下面的情況來分析,無論哪種情況,遞歸的這個節點,必然為黑色*/所以不需要考慮將根節點強轉黑色BinNode<T>* sibling = (replacer == p->_lchild) ? p->_rchild : p->_lchild;//原來x的兄弟,也就是replacer此時的兄弟if (IsBlack(sibling)) {BinNode<T>* s_Red_child = nullptr;//sibling的紅孩子(若左右孩子皆為紅,則左者優先;皆黑時為nullptr)if (IsRed(sibling->_rchild))//需要判斷sibling是不是空指針s_Red_child = sibling->_rchild;//右孩子if (IsRed(sibling->_lchild))s_Red_child = sibling->_lchild;//左孩子 /*1.第一種情況,黑s有紅孩子*/if (s_Red_child != nullptr) {//如果sibling有紅孩子RBColor oldColor = p->_color;//備份父親的顏色/*接下來對s的紅孩子,s以及p進行3+4重構*//*根據3+4重構后的定義,其返回的節點為根節點指針,這個根節點的名字不妨設為newNode*/BinNode<T>*& newNode = this->FromParentTo(p);//首先記錄父親的 父親的孩子的指針newNode = this->rotateAt(s_Red_child);//3+4重構//對3+4重構后的節點進行重染色if (HasLChild(newNode)) {newNode->_lchild->_color = RBColor::BLACK;updateHeight(newNode->_lchild);}if (HasRChild(newNode)) {newNode->_rchild->_color = RBColor::BLACK;updateHeight(newNode->_rchild);}newNode->_color = oldColor;//新子樹根節點繼承原根節點的顏色updateHeight(newNode);//更新高度/*至此,就調整完畢,紅黑樹恢復平衡*/return;//用遞歸的時候注釋掉}/*黑s沒有紅孩子,及其孩子均為黑色(可能為空),對其父親是否為紅色進行判斷*/else {sibling->_color = RBColor::RED;//無論父親是否為紅色,都需要把s設為紅色sibling->_height--; /*2.黑s只有黑孩子(可能為空,并且其父親為紅色*/if (IsRed(p)) {p->_color = RBColor::BLACK;//直接將父親設定為黑色,父親的高度必然沒有發生變化。//因為p原來為紅,現在由于兩個孩子高度都減了一,所以將其變成黑色后,其高度就恢復了原來的高度/*至此,就調整完畢,紅黑樹恢復平衡*/return;//用遞歸的時候注釋掉} /*3.黑s只有黑孩子(可能為空,并且其父親為黑色*/else {p->_height--;//相當于p的父親被刪,然后對p是父親的replacer,因此,對p進行遞歸既可。//solveDoubleBlack(p);//用遞歸時用replacer = p;//將replacer變成p進入迭代循環//用遞歸的時候注釋掉/*之后可能進入1 2 3 4四種情況中的任何一種,最壞可能到根*/}}} /*4.s為紅,此時其必然只有黑孩子(黑孩子可能均為空),當然s的父親p此時也必然為黑*/else {sibling->_color = RBColor::BLACK;//將s和p的顏色互換p->_color = RBColor::RED;//取與s同側的孩子BinNode<T>* s_child = IsLChild(sibling) ? sibling->_lchild : sibling->_rchild;this->_hot = p;//首先將p的父親記錄起來BinNode<T>*& newNode = this->FromParentTo(p);//首先記錄p的 父親的孩子的指針newNode = this->rotateAt(s_child);//將s_child,s與p 進行3+4重構/*調整之后,樹的局部結構就發生了變化*///solveDoubleBlack(replacer);//用遞歸時用//由第四種情況的分析,可知,只需要修復重構后的replacer就可以//并且之后只有可能進入第一種和第二種情況,必然不可能進入第三種情況}} }(7)雙黑修復復雜度~
??鄧老師已經用一個流程圖和一個表格,幫助我們詳細地分析了雙黑修復算法的復雜度。其中zag,zig是左右旋(也就是我們的connect34重構)
??其中涉及的重構、染色等局部操作,均可在常數時間內完成,故為了估計整個雙黑修正過程的時間復雜度,也只需統計這些操作各自的累計執行次數。
??情況BB-2-B雖可能需要反復修正,但由于待修正位置的高度嚴格單調上升,累計也不致過O(logn)輪,故雙黑修正過程總共耗時不超過O(logn)。
??即便計入此前的關鍵碼查找和節點摘除操作,紅黑樹的節點刪除操作總是可在O(logn)時間內完成。
??一旦在某步迭代中做過節點的旋轉調整,整個修復過程便會隨即完成。因此與雙紅修正一樣,雙黑修正的整個過程,也僅涉及常數次的拓撲結構調整操作。
??這同樣也是紅黑樹與AVL樹之間最本質的差別。(在本文章的開頭,就說明了AVL的不足就在于刪除時可能要多達logn次調整。)
五、完整RedBlack.h~
#pragma once #include "BinNode.h" #include "BST.h"namespace my_redblack {using mytree::BinNode;using mytree::BST;using mytree::RBColor;using mytree_marcro::stature;using mytree_marcro::IsRoot;using mytree_marcro::IsLChild;using mytree_marcro::HasLChild;using mytree_marcro::HasRChild;template<typename T=int>class RedBlack :public BST<T> {protected:using BinNodePtr = BinNode<T>*;protected:void solveDoubleRed(BinNode<T>* x);//雙紅修正void solveDoubleBlack(BinNode<T>* replacer);//雙黑修正constexpr int updateHeight(BinNode<T>* x)const override;//更新高度public:BinNode<T>* insert(const T& data)override;//插入重寫bool remove(const T& data)override;//刪除重寫/*查找沿用BST的查找*//*遍歷沿用BinTree的遍歷*/protected:static constexpr bool IsBlack(const BinNodePtr& x) {//判黑//當然x為空,也為黑色return ((!x) || (RBColor::BLACK == x->_color));}static constexpr bool IsRed(const BinNodePtr& x) {//非黑即紅return !IsBlack(x);}static constexpr bool IsBlackHeightBalanced(const BinNodePtr& x) {//判斷是否需要更新黑高度bool is_L_C_Equal = (stature(x->_lchild) == stature(x->_rchild));int rbHeight = (IsRed(x) ? stature(x->_lchild) : stature(x->_lchild) + 1);//對于rbHeight的計算而言,取左孩子還是右孩子,均一樣bool is_Height_Equal = (x->_height == rbHeight);return is_L_C_Equal && is_Height_Equal;//只要有一個為假,即為假//所以只要左孩子和右孩子高度相等,或者x沒有高度變化,就黑高度平衡 }static inline BinNodePtr uncle(const BinNodePtr& x) {/*獲取x的叔叔*/return IsLChild(x->_parent) ? x->_parent->_parent->_rchild : x->_parent->_parent->_lchild;}};//class RedBlacktemplate<typename T>constexpr int RedBlack<T>::updateHeight(BinNode<T>* x) const//由于stature視空節點高度為-1,所以height會比黑高度少一{x->_height = std::max(stature(x->_lchild), stature(x->_rchild));//孩子一般黑高度相等,除非出現雙黑return IsBlack(x) ? x->_height++ : x->_height;//若當前節點為黑,則計入黑高度}template<typename T>void RedBlack<T>::solveDoubleRed(BinNode<T>* x){while (true) {if (IsRoot(x)) {//若已迭代到樹根,則樹根轉黑,整樹高度也隨之遞增this->_root->_color = RBColor::BLACK;this->_root->_height++;return;}//否則x的父親必然存在BinNode<T>* p = x->_parent;//x的父親if (IsBlack(p))//如果x的父親為黑,則終止調整return;//否則x的父親必然為紅,則BinNode<T>* g = p->_parent;//x的祖父必然存在,并且,其顏色必然為黑色BinNode<T>* u = uncle(x);//x的叔叔,可能為空節點if (IsBlack(u)) {//當u為黑色時(u為空時,也為黑色)//if (IsLChild(x) == IsLChild(p))// p->_color = RBColor::BLACK;//else// x->_color = RBColor::BLACK;//g->_color = RBColor::RED;/// 以上雖保證總共兩次染色,但因增加了判斷而得不償失/// 在旋轉后將根置黑、孩子置紅,雖需三次染色但效率更高BinNode<T>*& newNode = this->FromParentTo(g);//先記錄祖父的父親的孩子指針newNode = this->rotateAt(x);/*對x進行調整,調整之后的返回值必然為調整之后的局部子樹的樹根位置,此時,只需要將此樹根作為原來祖父的父親的孩子既可,當然,高度也隨之更新*///重染色/*能省去之前的判斷*/newNode->_color = RBColor::BLACK;newNode->_lchild->_color = RBColor::RED;newNode->_rchild->_color = RBColor::RED;return;//只要旋轉了一次,就調整完整,退出循環}else {//若u為紅色p->_color = RBColor::BLACK;//父親此時必然為紅色,將其轉黑p->_height++;u->_color = RBColor::BLACK;//叔叔此時必然為紅色,將其轉黑u->_height++;if (!IsRoot(g))//如果祖父不為根節點,就轉紅g->_color = RBColor::RED;//solveDoubleRed(g);//遞歸用x = g;//將x變成其祖父,進入迭代循環。}}}template<typename T>BinNode<T>* RedBlack<T>::insert(const T& data){BinNode<T>*& x = this->search(data);//沿用BST的查找//并更新_hot//用引用接收if (x)//如果節點存在,則返回return x;x = new BinNode<T>(data, this->_hot, nullptr, nullptr, -1);//設定黑高度-1,并默認節點為紅色this->_size++;solveDoubleRed(x);//雙紅修正//x此時必為紅return x;}template<typename T>void RedBlack<T>::solveDoubleBlack(BinNode<T>* replacer){while (true) {BinNode<T>* p = replacer ? replacer->_parent : this->_hot;if (p == nullptr)return;//如果replacer的父親為空,則返回/*由下面的情況來分析,無論哪種情況,遞歸的這個節點,必然為黑色*/所以不需要考慮將根節點強轉黑色BinNode<T>* sibling = (replacer == p->_lchild) ? p->_rchild : p->_lchild;//原來x的兄弟,也就是replacer此時的兄弟if (IsBlack(sibling)) {BinNode<T>* s_Red_child = nullptr;//sibling的紅孩子(若左右孩子皆為紅,則左者優先;皆黑時為nullptr)if (IsRed(sibling->_rchild))//需要判斷sibling是不是空指針s_Red_child = sibling->_rchild;//右孩子if (IsRed(sibling->_lchild))s_Red_child = sibling->_lchild;//左孩子/*1.第一種情況,黑s有紅孩子*/if (s_Red_child != nullptr) {//如果sibling有紅孩子RBColor oldColor = p->_color;//備份父親的顏色/*接下來對s的紅孩子,s以及p進行3+4重構*//*根據3+4重構后的定義,其返回的節點為根節點指針,這個根節點的名字不妨設為newNode*/BinNode<T>*& newNode = this->FromParentTo(p);//首先記錄父親的 父親的孩子的指針newNode = this->rotateAt(s_Red_child);//3+4重構//對3+4重構后的節點進行重染色if (HasLChild(newNode)) {newNode->_lchild->_color = RBColor::BLACK;updateHeight(newNode->_lchild);}if (HasRChild(newNode)) {newNode->_rchild->_color = RBColor::BLACK;updateHeight(newNode->_rchild);}newNode->_color = oldColor;//新子樹根節點繼承原根節點的顏色updateHeight(newNode);//更新高度/*至此,就調整完畢,紅黑樹恢復平衡*/return;//用遞歸的時候注釋掉}/*黑s沒有紅孩子,及其孩子均為黑色(可能為空),對其父親是否為紅色進行判斷*/else {sibling->_color = RBColor::RED;//無論父親是否為紅色,都需要把s設為紅色sibling->_height--;/*2.黑s只有黑孩子(可能為空,并且其父親為紅色*/if (IsRed(p)) {p->_color = RBColor::BLACK;//直接將父親設定為黑色,父親的高度必然沒有發生變化。//因為p原來為紅,現在由于兩個孩子高度都減了一,所以將其變成黑色后,其高度就恢復了原來的高度/*至此,就調整完畢,紅黑樹恢復平衡*/return;//用遞歸的時候注釋掉}/*3.黑s只有黑孩子(可能為空,并且其父親為黑色*/else {p->_height--;//相當于p的父親被刪,然后對p是父親的replacer,因此,對p進行遞歸既可。//solveDoubleBlack(p);//用遞歸時用replacer = p;//將replacer變成p進入迭代循環//用遞歸的時候注釋掉/*之后可能進入1 2 3 4四種情況中的任何一種,最壞可能到根*/}}}/*4.s為紅,此時其必然只有黑孩子(黑孩子可能均為空),當然s的父親p此時也必然為黑*/else {sibling->_color = RBColor::BLACK;//將s和p的顏色互換p->_color = RBColor::RED;//取與s同側的孩子BinNode<T>* s_child = IsLChild(sibling) ? sibling->_lchild : sibling->_rchild;this->_hot = p;//首先將p的父親記錄起來BinNode<T>*& newNode = this->FromParentTo(p);//首先記錄p的 父親的孩子的指針newNode = this->rotateAt(s_child);//將s_child,s與p 進行3+4重構/*調整之后,樹的局部結構就發生了變化*///solveDoubleBlack(replacer);//用遞歸時用//由第四種情況的分析,可知,只需要修復重構后的replacer就可以//并且之后只有可能進入第一種和第二種情況,必然不可能進入第三種情況}}}template<typename T>bool RedBlack<T>::remove(const T& data){BinNode<T>*& x = this->search(data);//找有沒有這個節點,如果沒有,則返回false,記住用引用if (!x)return false;BinNode<T>* replacer = removeAt(x, this->_hot);//調用在BST定義的全局靜態函數removeAt,返回被刪除節點的接替者,同時更新_hot--this->_size;//更新規模//1.如果這個被刪除節點是樹中唯一節點,則直接返回if (this->_size==0) {this->_root = nullptr;//將根節點置空return true;}//2.如果被刪除節點為根節點,則_hot必然為空//但如果進行到此,說明此時_root必然不為空,不然上一就會退出if (this->_hot == nullptr) {this->_root->_color = RBColor::BLACK;//就將此時的根節點直接染成黑色updateHeight(this->_root);//并更新根節點的高度return true;}//如果進行到此,說明被刪除節點必然存在,并且不為根節點。_hot也必然存在/*3.如果_hot的黑高度不變則返回*//*此時也必然包括了被刪除節點為紅色節點的情況,若為紅色,則刪除對高度沒有影響*//*當然,也包括了雙黑的可能情況*/if (IsBlackHeightBalanced(this->_hot))return true;/*4.如果_hot的黑高度變了,說明被刪除節點必然為黑色*/if (IsRed(replacer)) {//就看x的接替者是不是紅色,如果是紅色,將其染成黑色,就必然可以使樹的高度恢復。replacer->_color = RBColor::BLACK;replacer->_height++;return true;}/*5.如果進行到此,就必然說明被刪除節點和replacer均為黑色節點(replacer可能為空),此時,就需要進行雙黑缺陷判斷*//*要進行到這里的條件即為被刪除節點的父親的黑高度變了,并且被刪除節點的接替者也為黑色時。*/solveDoubleBlack(replacer);return true;}}//namespace my_redblack六、紅黑樹測試~
1.插入測試代碼~
#include<iostream> #include "RedBlack.h" using namespace std; using namespace my_redblack;template<typename BinNodePtr> void visite(BinNodePtr x) {cout << "數據為:" << x->_data << " ";if (x->_color == RBColor::RED) {cout << "顏色為:" << "紅" << " ";}else {cout << "顏色為:" << "黑" << " ";}cout << endl; } int main() {RedBlack r;for (int i = 0; i < 10; ++i) {r.insert(i);}cout << "層次遍歷為:" << endl;r.travLevel(visite<BinNode<int>*>);cout << endl;cout << "先序遍歷為:" << endl;r.travPre(visite<BinNode<int>*>);cout << endl;cout << "中序遍歷為:" << endl;r.travIn(visite<BinNode<int>*>);cout << endl;cout << "后序遍歷為:" << endl;r.travPost(visite<BinNode<int>*>);cout << endl;return 0; } 層次遍歷為: 數據為:3 顏色為:黑 數據為:1 顏色為:黑 數據為:5 顏色為:黑 數據為:0 顏色為:黑 數據為:2 顏色為:黑 數據為:4 顏色為:黑 數據為:7 顏色為:紅 數據為:6 顏色為:黑 數據為:8 顏色為:黑 數據為:9 顏色為:紅先序遍歷為: 數據為:3 顏色為:黑 數據為:1 顏色為:黑 數據為:0 顏色為:黑 數據為:2 顏色為:黑 數據為:5 顏色為:黑 數據為:4 顏色為:黑 數據為:7 顏色為:紅 數據為:6 顏色為:黑 數據為:8 顏色為:黑 數據為:9 顏色為:紅中序遍歷為: 數據為:0 顏色為:黑 數據為:1 顏色為:黑 數據為:2 顏色為:黑 數據為:3 顏色為:黑 數據為:4 顏色為:黑 數據為:5 顏色為:黑 數據為:6 顏色為:黑 數據為:7 顏色為:紅 數據為:8 顏色為:黑 數據為:9 顏色為:紅后序遍歷為: 數據為:0 顏色為:黑 數據為:2 顏色為:黑 數據為:1 顏色為:黑 數據為:4 顏色為:黑 數據為:6 顏色為:黑 數據為:9 顏色為:紅 數據為:8 顏色為:黑 數據為:7 顏色為:紅 數據為:5 顏色為:黑 數據為:3 顏色為:黑2.插入測試圖示~
3.刪除測試代碼~
#include<iostream> #include "RedBlack.h" using namespace std; using namespace my_redblack;template<typename BinNodePtr> void visite(BinNodePtr x) {cout << "數據為:" << x->_data << " ";if (x->_color == RBColor::RED) {cout << "顏色為:" << "紅" << " ";}else {cout << "顏色為:" << "黑" << " ";}cout << endl; } int main() {RedBlack r;for (int i = 0; i < 10; ++i) {r.insert(i);}cout << "中序遍歷為:" << endl;r.travIn(visite<BinNode<int>*>);cout << endl;for (int i = 0; i < 10; ++i) {r.remove(i);r.travIn(visite<BinNode<int>*>);cout << endl;}return 0; } 中序遍歷為: 數據為:0 顏色為:黑 數據為:1 顏色為:黑 數據為:2 顏色為:黑 數據為:3 顏色為:黑 數據為:4 顏色為:黑 數據為:5 顏色為:黑 數據為:6 顏色為:黑 數據為:7 顏色為:紅 數據為:8 顏色為:黑 數據為:9 顏色為:紅數據為:1 顏色為:黑 數據為:2 顏色為:紅 數據為:3 顏色為:黑 數據為:4 顏色為:黑 數據為:5 顏色為:黑 數據為:6 顏色為:黑 數據為:7 顏色為:黑 數據為:8 顏色為:黑 數據為:9 顏色為:紅數據為:2 顏色為:黑 數據為:3 顏色為:黑 數據為:4 顏色為:黑 數據為:5 顏色為:黑 數據為:6 顏色為:黑 數據為:7 顏色為:黑 數據為:8 顏色為:黑 數據為:9 顏色為:紅數據為:3 顏色為:黑 數據為:4 顏色為:紅 數據為:5 顏色為:黑 數據為:6 顏色為:黑 數據為:7 顏色為:紅 數據為:8 顏色為:黑 數據為:9 顏色為:紅數據為:4 顏色為:黑 數據為:5 顏色為:黑 數據為:6 顏色為:黑 數據為:7 顏色為:紅 數據為:8 顏色為:黑 數據為:9 顏色為:紅數據為:5 顏色為:黑 數據為:6 顏色為:紅 數據為:7 顏色為:黑 數據為:8 顏色為:黑 數據為:9 顏色為:紅數據為:6 顏色為:黑 數據為:7 顏色為:黑 數據為:8 顏色為:黑 數據為:9 顏色為:紅數據為:7 顏色為:黑 數據為:8 顏色為:黑 數據為:9 顏色為:黑數據為:8 顏色為:黑 數據為:9 顏色為:紅數據為:9 顏色為:黑4.刪除測試圖示~
七、結語~
本系列文章,總共6篇,分別介紹了
??其中B樹和紅黑樹的難度最大的,這兩種樹情況分析確實十分復雜,除非背下來,不然是不可能在短時間內一下就能分析出這么多情況,并且不發生遺漏的。
??在此,再次感謝鄧老師的教材和視頻,我才能夠掌握樹的核心框架。并且感謝發明這些樹的前輩們,多虧了他們的智慧,才有了這些高效的數據結構和算法的出現,提高了我們的計算機處理問題的能力。
??并且不知不覺,算上代碼,此系列文章也寫了將近9w字。零零散散大概寫了兩個星期左右。初衷是想要鍛煉一下c++的編程能力以及數據結構和算法。但寫著寫著就想盡可能地將內容完善,簡潔,并且簡單易懂地描述各種插入和刪除的過程。
??我相信,只要你從頭看下來,你對樹的理解必然會上升一個檔次。如果你對某些地方的算法有所困惑,那不妨將其手敲一遍,對照著文章再看一遍,或者看下鄧老師的書和講解,你一定能理解這里為什么要用這種算法。
??c++就我個人理解而言,是一門非常嚴謹的語言。寫c++代碼的時候,要考慮的因素很多,因此可能初學起來相對java,c#等語言而言,會感到進度緩慢,但只要入門了之后,特別是了解了c++11常用特性,以及c++的常用語法之后,你會對c++越來越喜愛,也會明白為什么c++是世界上效率最高的語言。
??廢話不多說,感謝你能耐心看完我精心準備的此系列文章,雖然我已經和鄧老師的代碼的各個實現進行了比對,但難免會有所遺漏之處,希望廣大讀者能夠即時進行指正。
??如果你覺得對你有用,請不要光收藏,你的贊是我繼續分享好東西的動力。
另外,借用侯捷大師的話:
學一個東西,不知道其道理,不高明!
總結
以上是生活随笔為你收集整理的真c++ 从二叉树到红黑树(6)之红黑树RedBlack的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 最新互联网乡镇综治云平台解决方案
- 下一篇: 顾客点餐系统-----后端代码编写(基于