红黑树及其操作
2019獨角獸企業重金招聘Python工程師標準>>>
簡單介紹
紅黑樹(RB-Tree)是根據節點顏色來調整的近平衡二叉樹,也是查找樹(中序遍歷從大到小)的一種,沒有平衡二叉樹那么嚴格,但是性能與其差不多。
紅黑樹的5個性質:
?
正是紅黑樹的這5條性質,使一棵n個結點的紅黑樹始終保持了logn的高度(紅黑樹的高度至多為2log(n+1)證明略),從而也就解釋了上面所說的“紅黑樹的查找、插入、刪除的時間復雜度最壞為O(log?n)”這一結論成立的原因。?
此圖忽略了葉子和根部的父結點。同時,上文中我們所說的 "葉結點" 或"NULL結點",如上圖所示,它不包含數據而只充當樹在此結束的指示,這些節點在繪圖中經常被省略,望看到此文后的讀者朋友注意。
約定:
要插入的節點為,N
父親節點,P
祖父節點,G
叔叔節點,U
兄弟節點,S
紅黑樹的插入
(1)情形1: 新節點N位于樹的根上,沒有父節點
當前節點為根節點,必為黑色
void insert_case1(node n) {
???? if (n->parent == NULL)
???????? n->color = BLACK;
???? else
???????? insert_case2(n);
?}
(2)情形2:?新節點的父節點P是黑色
插入后仍為紅黑樹,無須變
void insert_case2(node n) {
???? if (n->parent->color == BLACK)
???????? return; /* 樹仍舊有效 */
???? else
???????? insert_case3(n);
?}
(3)情形3:父節點P、叔叔節點U,都為紅色
此時父結點的父結點一定存在,否則插入前就已不是紅黑樹。與此同時,又分為父結點是祖父結點的左孩子還是右孩子,根據對稱性,我們只要解開一個方向就可以了。這里只考慮父結點為祖父左孩子的情況,如下圖所示。
?
對此,我們的解決策略是:將當前節點的父節點和叔叔節點涂黑,祖父結點涂紅,把當前結點指向祖父節點,從新的當前節點重新開始算法。即如下代碼所示:
void insert_case3(node n) {
???? if (uncle(n) != NULL && uncle(n)->color == RED) {
???????? n->parent->color = BLACK;
???????? uncle(n)->color = BLACK;
???????? grandparent(n)->color = RED;
???????? insert_case1(grandparent(n));?? //因為祖父節點可能是紅色的,違反性質4,遞歸情形1.
???? }
???? else
???????? insert_case4(n);?? //否則,叔叔是黑色的,轉到下述情形4處理。
此時新插入節點N做為P的左子節點或右子節點都屬于上述情形3,上圖僅顯示N做為P左子的情形
(4)情形4: 父節點P是紅色,叔叔節點U是黑色或NIL,且當前節點、父節點、祖父節點不在一條線
當前節點的父節點是紅色,叔叔節點是黑色,
1)當前節點是其父節點的右子且其父節點是祖父的左邊子,,則左旋
2)當前節點是其父節點的左子且其父節點是祖父的右邊子,,則右轉
最終的目的是想當前節點、父節點、祖父節點在用一條直線上,列舉上面1)中的情況變化前后比較
紅黑樹變之前的:
?
變化成:
轉為一條線上后可進行情形5操作,具體代碼如下:
void insert_case4(node n) {
???? if (n == n->parent->right && n->parent == grandparent(n)->left) {
???????? rotate_left(n->parent);
???????? n = n->left;
???? } else if (n == n->parent->left && n->parent == grandparent(n)->right) {
???????? rotate_right(n->parent);
???????? n = n->right;
???? }
???? insert_case5(n);??? //轉到下述情形5處理。
(5)情形5: 父節點P是紅色,叔叔節點U是黑色或NIL,且當前節點、父節點、祖父節點在一條線
解決對策是:父節點變為黑色,祖父節點變為紅色,在祖父節點為支點右旋或左旋,具體是當前節點、父親、祖父所在直線往左,則右旋,反之亦然。
所以紅黑樹由之前的:
變化成:
具體代碼為:
void insert_case5(node n) {
???? n->parent->color = BLACK;
???? grandparent(n)->color = RED;
???? if (n == n->parent->left && n->parent == grandparent(n)->left) {
???????? rotate_right(grandparent(n));
???? } else {
???????? /* 反情況,N 是其父節點的右孩子,而父節點P又是其父G的右孩子 */
???????? rotate_left(grandparent(n));
???? }
?}
參考算法導論第三版13.3-2題目:41、38、31、12、19、8依次插入為空的紅黑樹
更多從無到有構建實例可以參考http://blog.csdn.net/jimoshuicao/article/details/8618043
紅黑樹的刪除
"我們刪除的節點的方法與常規二叉搜索樹中刪除節點的方法是一樣的,如果被刪除的節點不是有雙非空子女,則直接刪除這個節點,用它的唯一子節點頂替它的位置,如果它的子節點分是空節點,那就用空節點頂替它的位置,如果它的雙子全為非空,我們就把它的直接后繼節點內容復制到它的位置,之后以同樣的方式刪除它的后繼節點,它的后繼節點不可能是雙子非空,因此此傳遞過程最多只進行一次。”
二叉查找樹的刪除
繼續講解之前,補充說明下二叉樹結點刪除的幾種情況,待刪除的節點按照兒子的個數可以分為三種:
“在刪除節點后,原紅黑樹的性質可能被改變,如果刪除的是紅色節點,那么原紅黑樹的性質依舊保持,此時不用做修正操作,如果刪除的節點是黑色節點,原紅黑樹的性質可能會被改變,我們要對其做修正操作。那么哪些樹的性質會發生變化呢,如果刪除節點不是樹唯一節點,那么刪除節點的那一個支的到各葉節點的黑色節點數會發生變化,此時性質5被破壞。如果被刪節點的唯一非空子節點是紅色,而被刪節點的父節點也是紅色,那么性質4被破壞。如果被刪節點是根節點,而它的唯一非空子節點是紅色,則刪除后新根節點將變成紅色,違背性質2。”
“上面的修復情況看起來有些復雜,下面我們用一個分析技巧:我們從被刪節點后來頂替它的那個節點開始調整,并認為它有額外的一重黑色。這里額外一重黑色是什么意思呢,我們不是把紅黑樹的節點加上除紅與黑的另一種顏色,這里只是一種假設,我們認為我們當前指向它,因此空有額外一種黑色,可以認為它的黑色是從它的父節點被刪除后繼承給它的,它現在可以容納兩種顏色,如果它原來是紅色,那么現在是紅+黑,如果原來是黑色,那么它現在的顏色是黑+黑。有了這重額外的黑色,原紅黑樹性質5就能保持不變。現在只要恢復其它性質就可以了,做法還是盡量向根移動和窮舉所有可能性。"--saturnman。
如果是以下情況,恢復比較簡單:
????a)當前節點是紅+黑色
????????????解法,直接把當前節點染成黑色,結束此時紅黑樹性質全部恢復。
????b)當前節點是黑+黑且是根節點,
????????????解法:什么都不做,結束。
????c)但如果是以下情況呢?:
- 刪除修復情況1:當前節點是黑+黑且兄弟節點為紅色(此時父節點和兄弟節點的子節點分為黑)
- 刪除修復情況2:當前節點是黑加黑且兄弟是黑色且兄弟節點的兩個子節點全為黑色
- 刪除修復情況3:當前節點顏色是黑+黑,兄弟節點是黑色,兄弟的左子是紅色,右子是黑色
- 刪除修復情況4:當前節點顏色是黑-黑色,它的兄弟節點是黑色,但是兄弟節點的右子是紅色,兄弟節點左子的顏色任意
下面,咱們便來分別來處理所有的刪除修復情況。
(1)情況1:?N 是新的根。
void delete_case1(struct node *n)
{
??????? if (n->parent != NULL)
??????????????? delete_case2(n);
}
(2)情形2:兄弟節點S是紅色
解法:把父節點染成紅色,把兄弟結點染成黑色,之后重新進入算法(我們只討論當前節點是其父節點左孩子時的情況)。此變換后原紅黑樹性質5不變,而把問題轉化為兄弟節點為黑色的情況(注:變化前,原本就未違反性質5,只是為了把問題轉化為兄弟節點為黑色的情況)。 即如下代碼操作
void delete_case2(struct node *n)
{
??????? struct node *s = sibling(n);
?
??????? if (s->color == RED) {
??????????????? n->parent->color = RED;
??????????????? s->color = BLACK;
??????????????? if (n == n->parent->left)
??????????????????????? rotate_left(n->parent);? //左旋
??????????????? else
??????????????????????? rotate_right(n->parent);
??????? }?
??????? delete_case3(n);
}
變化前:
?
變化后:?
?
情況 3: 兄弟節點S是黑色的,且S的倆個兒子都是黑色的。但N的父節點P,是黑色。
解法:把當前節點和兄弟節點中抽取一重黑色追加到父節點上,把父節點當成新的當前節點,重新進入算法。(此變換后性質5不變),
void delete_case3(struct node *n)
{
??????? struct node *s = sibling(n);
?
??????? if ((n->parent->color == BLACK) &&
??????????? (s->color == BLACK) &&
??????????? (s->left->color == BLACK) &&
??????????? (s->right->color == BLACK)) {
??????????????? s->color = RED;
??????????????? delete_case1(n->parent);
??????? } else
??????????????? delete_case4(n);
}
?
情況4:?兄弟節點S 是黑色的、S 的兒子也都是黑色的,但是 N 的父親P,是紅色。
[還是對應我第二篇文章中,情況2:x的兄弟w是黑色的,且w的倆個孩子都是黑色的。
(這里,父節點P為紅)]
void delete_case4(struct node *n)
{
??????? struct node *s = sibling(n);
?
??????? if ((n->parent->color == RED) &&
??????????? (s->color == BLACK) &&
??????????? (s->left->color == BLACK) &&
??????????? (s->right->color == BLACK)) {
??????????????? s->color = RED;
??????????????? n->parent->color = BLACK;
??????? } else
??????????????? delete_case5(n);
}
情況5:?兄弟S為黑色,S 的左兒子是紅色,S 的右兒子是黑色,而N是它父親的左兒子。
//此種情況,最后轉化到下面的情況6。
[對應我第二篇文章中,情況3:x的兄弟w是黑色的,w的左孩子是紅色,w的右孩子是黑色。]
void delete_case5(struct node *n)
{
??????? struct node *s = sibling(n);
?
??????? if? (s->color == BLACK)?
??????????????? if ((n == n->parent->left) &&
??????????????????? (s->right->color == BLACK) &&
??????????????????? (s->left->color == RED)) {?
??????????????????????? // this last test is trivial too due to cases 2-4.
??????????????????????? s->color = RED;
??????????????????????? s->left->color = BLACK;
??????????????????????? rotate_right(s);
??????????????? } else if ((n == n->parent->right) &&
?????????????????????????? (s->left->color == BLACK) &&
?????????????????????????? (s->right->color == RED)) {
?????????????????????? // this last test is trivial too due to cases 2-4.
??????????????????????? s->color = RED;
??????????????????????? s->right->color = BLACK;
??????????????????????? rotate_left(s);
??????????????? }
??????? }
??????? delete_case6(n);? //轉到情況6。
?
情況6:?兄弟節點S是黑色,S的右兒子是紅色,而 N 是它父親的左兒子。
[對應我第二篇文章中,情況4:x的兄弟w是黑色的,且w的右孩子時紅色的。]
void delete_case6(struct node *n)
{
??????? struct node *s = sibling(n);
?
??????? s->color = n->parent->color;
??????? n->parent->color = BLACK;
?
??????? if (n == n->parent->left) {
??????????????? s->right->color = BLACK;
??????????????? rotate_left(n->parent);
??????? } else {
??????????????? s->left->color = BLACK;
??????????????? rotate_right(n->parent);
??????? }
}
?
紅黑樹的插入、刪除情況時間復雜度的分析
因為每一個紅黑樹也是一個特化的二叉查找樹,
因此紅黑樹上的只讀操作與普通二叉查找樹上的只讀操作相同。
然而,在紅黑樹上進行插入操作和刪除操作會導致不再符合紅黑樹的性質。
恢復紅黑樹的屬性需要少量(O(log n))的顏色變更(實際是非常快速的)和
不超過三次樹旋轉(對于插入操作是兩次)。
雖然插入和刪除很復雜,但操作時間仍可以保持為 O(log n) 次。
ok,完。
后記:
此紅黑樹系列,前前后后,已經寫了4篇文章,如果讀者讀完了這4篇文章,
對紅黑樹有一個相對之前來說,比較透徹的理解,
那么,也不枉費,我花這么多篇幅、花好幾個鐘頭去畫紅黑樹了。
真正理解一個數據結構、算法,最緊要的還是真正待用、實踐的時候體會。
歡迎,各位,將現在、或以后學習、工作中運用此紅黑樹結構、算法的經驗與我分享。
謝謝。:D。
?
參考文獻:
第一篇:教你透徹了解紅黑樹:
http://blog.csdn.net/v_JULY_v/archive/2010/12/29/6105630.aspx
第二篇:紅黑樹算法的層層剖析與逐步實現
http://blog.csdn.net/v_JULY_v/archive/2010/12/31/6109153.aspx
第三篇:教你徹底實現紅黑樹:紅黑樹的c源碼實現與剖析
http://blog.csdn.net/v_JULY_v/archive/2011/01/03/6114226.aspx
第四篇:一步一圖一代碼,一定要讓你真正徹底明白紅黑樹
一步一圖一代碼,一定要讓你真正徹底明白紅黑樹
?
轉載于:https://my.oschina.net/zlb1992/blog/869447
總結
- 上一篇: Android使用ViewPager+P
- 下一篇: 如何在github上fork一个项目来贡