本篇文章深入講述紅黑樹的原理與實現。紅黑樹是一個非常重要的數據結構,其本質上是一個平衡查找樹,可是其“平衡”的條件和AVL樹不同。在深入瞭解紅黑樹之前要先熟悉平衡查找樹的相關知識,這裏就不再介紹。
紅黑樹簡介
紅黑樹性質
首先,什麼是紅黑樹?紅黑樹(RB-Tree)是一種平衡二叉搜索樹,除此之外還要滿足以下規則:
- 每個節點不是紅色就是黑色
- 根節點爲黑色
- 如果節點爲紅色,其子節點必爲黑色
- 任意節點至NULL(樹末尾)的任何路徑所含的黑色節點數相同
- 每一個NULL視爲黑色節點
根據以上五條規則,可以推出一些推論:
- 新插入的節點必爲紅色節點(規則4所示,如果新增節點爲黑色則會使這條路徑上的黑色節點數比其他路徑多1,則必須要調整紅黑樹)
- 一顆n個節點的紅黑樹,其樹高度至多爲2log(n+1)
紅黑樹應用場景與比較
由於紅黑樹插入、刪除、查找的時間複雜度都爲O(logn),所以十分利於在存儲大量數據中進行查找。接下來說一下紅黑樹的具體應用場景。
- 首先在STL中,關聯容器中map與set都是以紅黑樹作爲底層實現。
- linux內核中有大量用到紅黑樹的地方,最典型的就是epoll將監聽事件的fd存儲在紅黑樹中,可以快熟查找和添加,又比如高精度計時器使用紅黑樹樹組織定時請求,EXT3文件系統也使用紅黑樹樹來管理目錄,虛擬存儲管理系統也有用紅黑樹進行管理等。
但是普通的平衡查找樹的查詢時間複雜度也是O(logn),那紅黑樹的優勢在哪呢?實際上,平衡查找樹對於平衡條件的控制非常嚴格,如AVL樹要求任何一個節點的左右子樹高度差不超過1,但是其實維持樹平衡狀態的開銷很大,紅黑樹的優勢就在這,紅黑樹對於“平衡條件”的控制不太嚴格,只要滿足其性質就是屬於平衡狀態,所以其對於維持樹平衡的開銷較小,也是其最大的優勢。
與此同時,跳錶(skiplist)的插入和查詢時間複雜度也是O(logn),並且跳錶的實現較紅黑樹而言簡單太多,因此Redis,LevelDB的MemTable中都採用跳錶作爲基本數據結構;除此之外,由於平衡查找樹和跳錶都是有序的(而哈希表是無序的),在範圍查找的過程中跳錶也比紅黑樹容易很多。具體參考Redis的數據結構
紅黑樹節點插入
接下來正式進入分析。
紅黑樹插入操作的時間複雜度爲O(logn),具體步驟如下:
- 先把其當做二叉搜索樹,將節點插入適當的位置(若插入的節點值比當前節點小則往左子樹移動,否則往右子樹移動);
- 按照推論1,將新插入的節點設置爲紅色;
- 將樹經過轉化變成一顆紅黑樹。分成三種情況:①若插入節點爲根節點,則把該節點變成黑色即可(性質2)②若插入節點的父節點爲黑色,則不需要做任何事情還是一顆紅黑樹(因爲沒有破壞任何一個性質)③若插入節點的父節點爲紅色,則又分成三種情況,如表格所示:
條件 | 步驟 |
---|---|
新加入的節點的叔節點爲紅色(根據性質,祖父節點必爲黑色) | 1. 父親節點設爲黑節點 2.叔叔節點設爲黑節點 3.祖父節點設爲紅節點 4. 以祖父節點爲新節點繼續重複 |
新加入的節點的叔節點爲黑色,且新加入的節點是內側插入(雙旋轉,也可以理解爲兩次單旋轉) | 1. 將父親節點進行左旋 2. 改變祖父節點和自己節點的顏色 3. 祖父節點進行右旋 |
新加入的節點的叔節點爲黑色,且新加入的節點是外側插入(單旋轉) | 1. 將祖父節點設爲紅色 2. 父節點設爲黑色 3. 右旋祖父節點 |
到此,紅黑樹的插入規則就闡述完了。
紅黑樹節點刪除
紅黑樹刪除操作時間複雜度也爲O(logn),具體的實施步驟比插入操作複雜一點。但是也可以分成多步驟進行。
- 按照二叉查找樹的方式定位並刪除節點。分成三種情況:①被刪除的節點沒有子節點,則直接刪除即可 ②如果被刪除的節點只有一個子節點,則刪除該節點並用其唯一子節點替代其位置 ③如果被刪除的節點有兩個子節點,則選取後繼子節點(即左子樹的最右節點),將後繼節點的值替換到被刪除的節點,然後刪除後繼節點(如果後繼節點有左子節點,則連接上即可)。
- 調整平衡。調整平衡的過程中分成三種情況: ①刪除的節點是紅色,則不需調整 ②如果刪除的節點是黑色且爲根節點,則不需調整 ③如果刪除的節點是黑色且不爲根節點,則又可以分成四種情況,如下所示:
條件 | 策略 |
---|---|
如果被刪除節點的兄弟節點是紅色(此時父節點與兄弟節點的子節點都是黑色) | 1. 將兄弟節點設爲黑色 2. 將父節點設爲紅色 3. 對父節點進行左旋 4. 左旋後,重新設置兄弟節點。 |
如果被刪除節點的兄弟節點是黑色,且兄弟節點的子節點都是黑色 | 1. 將兄弟節點設爲紅色 2. 設置父節點爲“新的被刪除節點”。 |
如果被刪除節點的兄弟節點是黑色,且兄弟節點的左孩子是紅色,右孩子是黑色 | 1. 將兄弟節點的左孩子設爲黑色 2. 將兄弟節點設爲紅色 3. 對兄弟節點進行右旋 4. 右旋後,重新設置兄弟節點。 |
如果被刪除節點的兄弟節點是黑色,且兄弟節點的右孩子是紅色 | 1. 將父節點顏色賦值給兄弟節點 2. 將父節點設爲黑色 3. 將兄弟節點的右子節設爲黑色 4. 對父節點進行左旋 5. 設置被刪除節點爲根節點。 |