深入理解數據結構——紅黑樹

本篇文章深入講述紅黑樹的原理與實現。紅黑樹是一個非常重要的數據結構,其本質上是一個平衡查找樹,可是其“平衡”的條件和AVL樹不同。在深入瞭解紅黑樹之前要先熟悉平衡查找樹的相關知識,這裏就不再介紹。

紅黑樹簡介

紅黑樹性質

首先,什麼是紅黑樹?紅黑樹(RB-Tree)是一種平衡二叉搜索樹,除此之外還要滿足以下規則:

  1. 每個節點不是紅色就是黑色
  2. 根節點爲黑色
  3. 如果節點爲紅色,其子節點必爲黑色
  4. 任意節點至NULL(樹末尾)的任何路徑所含的黑色節點數相同
  5. 每一個NULL視爲黑色節點

根據以上五條規則,可以推出一些推論:

  • 新插入的節點必爲紅色節點(規則4所示,如果新增節點爲黑色則會使這條路徑上的黑色節點數比其他路徑多1,則必須要調整紅黑樹)
  • 一顆n個節點的紅黑樹,其樹高度至多爲2log(n+1)
    一顆典型紅黑樹

紅黑樹應用場景與比較

由於紅黑樹插入、刪除、查找的時間複雜度都爲O(logn),所以十分利於在存儲大量數據中進行查找。接下來說一下紅黑樹的具體應用場景。

  1. 首先在STL中,關聯容器中map與set都是以紅黑樹作爲底層實現。
  2. linux內核中有大量用到紅黑樹的地方,最典型的就是epoll將監聽事件的fd存儲在紅黑樹中,可以快熟查找和添加,又比如高精度計時器使用紅黑樹樹組織定時請求,EXT3文件系統也使用紅黑樹樹來管理目錄,虛擬存儲管理系統也有用紅黑樹進行管理等。

但是普通的平衡查找樹的查詢時間複雜度也是O(logn),那紅黑樹的優勢在哪呢?實際上,平衡查找樹對於平衡條件的控制非常嚴格,如AVL樹要求任何一個節點的左右子樹高度差不超過1,但是其實維持樹平衡狀態的開銷很大,紅黑樹的優勢就在這,紅黑樹對於“平衡條件”的控制不太嚴格,只要滿足其性質就是屬於平衡狀態,所以其對於維持樹平衡的開銷較小,也是其最大的優勢。
與此同時,跳錶(skiplist)的插入和查詢時間複雜度也是O(logn),並且跳錶的實現較紅黑樹而言簡單太多,因此Redis,LevelDB的MemTable中都採用跳錶作爲基本數據結構;除此之外,由於平衡查找樹和跳錶都是有序的(而哈希表是無序的),在範圍查找的過程中跳錶也比紅黑樹容易很多。具體參考Redis的數據結構

紅黑樹節點插入

接下來正式進入分析。
紅黑樹插入操作的時間複雜度爲O(logn),具體步驟如下:

  1. 先把其當做二叉搜索樹,將節點插入適當的位置(若插入的節點值比當前節點小則往左子樹移動,否則往右子樹移動);
  2. 按照推論1,將新插入的節點設置爲紅色;
  3. 將樹經過轉化變成一顆紅黑樹。分成三種情況:①若插入節點爲根節點,則把該節點變成黑色即可(性質2)②若插入節點的父節點爲黑色,則不需要做任何事情還是一顆紅黑樹(因爲沒有破壞任何一個性質)③若插入節點的父節點爲紅色,則又分成三種情況,如表格所示:
條件 步驟
新加入的節點的叔節點爲紅色(根據性質,祖父節點必爲黑色) 1. 父親節點設爲黑節點 2.叔叔節點設爲黑節點 3.祖父節點設爲紅節點 4. 以祖父節點爲新節點繼續重複
新加入的節點的叔節點爲黑色,且新加入的節點是內側插入(雙旋轉,也可以理解爲兩次單旋轉) 1. 將父親節點進行左旋 2. 改變祖父節點和自己節點的顏色 3. 祖父節點進行右旋
新加入的節點的叔節點爲黑色,且新加入的節點是外側插入(單旋轉) 1. 將祖父節點設爲紅色 2. 父節點設爲黑色 3. 右旋祖父節點

到此,紅黑樹的插入規則就闡述完了。

紅黑樹節點刪除

紅黑樹刪除操作時間複雜度也爲O(logn),具體的實施步驟比插入操作複雜一點。但是也可以分成多步驟進行。

  1. 按照二叉查找樹的方式定位並刪除節點。分成三種情況:①被刪除的節點沒有子節點,則直接刪除即可 ②如果被刪除的節點只有一個子節點,則刪除該節點並用其唯一子節點替代其位置 ③如果被刪除的節點有兩個子節點,則選取後繼子節點(即左子樹的最右節點),將後繼節點的值替換到被刪除的節點,然後刪除後繼節點(如果後繼節點有左子節點,則連接上即可)。
  2. 調整平衡。調整平衡的過程中分成三種情況: ①刪除的節點是紅色,則不需調整 ②如果刪除的節點是黑色且爲根節點,則不需調整 ③如果刪除的節點是黑色且不爲根節點,則又可以分成四種情況,如下所示:
條件 策略
如果被刪除節點的兄弟節點是紅色(此時父節點與兄弟節點的子節點都是黑色) 1. 將兄弟節點設爲黑色 2. 將父節點設爲紅色 3. 對父節點進行左旋 4. 左旋後,重新設置兄弟節點。
如果被刪除節點的兄弟節點是黑色,且兄弟節點的子節點都是黑色 1. 將兄弟節點設爲紅色 2. 設置父節點爲“新的被刪除節點”。
如果被刪除節點的兄弟節點是黑色,且兄弟節點的左孩子是紅色,右孩子是黑色 1. 將兄弟節點的左孩子設爲黑色 2. 將兄弟節點設爲紅色 3. 對兄弟節點進行右旋 4. 右旋後,重新設置兄弟節點。
如果被刪除節點的兄弟節點是黑色,且兄弟節點的右孩子是紅色 1. 將父節點顏色賦值給兄弟節點 2. 將父節點設爲黑色 3. 將兄弟節點的右子節設爲黑色 4. 對父節點進行左旋 5. 設置被刪除節點爲根節點。

紅黑樹的實現

參考:
1. 《算法導論》
2. 《STL源碼剖析》
3. 紅黑樹原理概述
4. 紅黑樹刪除操作

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章