第 44 篇原創好文~
本文首發於政採雲前端團隊博客: 通俗易懂的紅黑樹圖解(下)
回顧一下通俗易懂的紅黑樹圖解(上),上篇首先介紹了二叉樹的定義以及二叉樹的查找,而後介紹了紅黑樹的五點性質以及紅黑樹的變色、左旋以及右旋等操做,最後結合變色、左旋及右旋詳細講解了插入節點的五種場景。而本篇通俗易懂的紅黑樹圖解(下)是在上篇的基礎上講解紅黑樹最後一種操做-刪除節點,刪除節點相對插入節點會複雜一點,但經過分類概括出不一樣的場景,能更容易理解和記憶。html
紅黑樹刪除操做包括兩部分,一是查找到刪除節點,二是刪除節點以及刪除以後的自平衡。查找節點與二叉樹的查找方式同樣。而刪除操做,當刪除節點不存在時,結束本次刪除操做;當刪除節點存在時,刪除節點,而後找到一個節點替換已刪除的節點位置,從新鏈接上已刪除節點的父節點與孩子節點。前端
以下圖,刪除節點 D ,須要找到一個節點能夠替換到 D 節點位置,不然節點 P 和節點 L 及 R 之間的連接會斷開,破壞了紅黑樹的性質,造成獨立的樹形結構。
關鍵字:查找節點
替換節點
node
查找刪除節點與二叉樹查找節點邏輯相同,經過與當前節點值比較,返回當前節點或者繼續從左子樹或者右子樹繼續查找。算法
在二叉查找樹中查找節點 N ,首先從根節點開始,將根節點設置爲當前節點,若當前節點爲空,則查找失敗,若 N 與當前節點值相等,返回當前節點,若 N 大於當前節點值,則從當前節點的右子節點開始查找,不然從當前節點的左子節點開始查找,直到返回目標節點或者查找失敗;
回顧一下二叉查找樹的性質:數據結構
根據二叉查找樹的性質,刪除節點以後,能夠找到兩個替換節點,便可以用左子樹中的最大值以及右子樹中的最小值來替換刪除節點。框架
刪除節點找替換節點又分三種情景:post
後繼節點:刪除節點的右子樹中的最小節點,即右子樹中最左節點。前繼節點:刪除節點的左子樹中最大節點,即左子樹中最右節點。性能
綜上所述,尋找一個節點替換已刪除節點位置,在不考慮節點值狀況下,可等同於刪除替換節點。網站
刪除節點可等同於刪除替換節點,因此節點刪除就轉換到了替換節點的各類場景。節點刪除又分 9 種場景,在以下的描述場景中,場景 2 中的四種狀況與場景 3 中的四種狀況分別互爲鏡像,可參照對比着看。this
即替換的節點是紅色節點,刪除以後不影響紅黑樹的平衡,只須要把替換節點的顏色設成被刪除節點的顏色便可從新平衡。
處理: 刪除節點D,查找到替換節點R,R設成D節點的顏色,再替換D節點位置。
刪除場景 2:替換節點是黑色節點、且是其父節點的左子節點
替換節點是黑色節點時,刪除以後破壞了紅黑樹的平衡,須要考慮自平衡處理。而此又細分爲 4 種場景。
場景 2.1:替換節點的兄弟節點是紅色。刪除黑色節點,左子樹中黑色節點數減小一個,能夠經過一些操做,達到間接借用紅色的兄弟節點來補充左子樹中黑色節點數。
處理:替換節點的父節點 P 設置紅色、兄弟節點 S 設置成黑色,再對節點 P 左旋操做,變成場景 2.4。
場景 2.2:替換節點的兄弟節點是黑色且兄弟節點的右子節點是紅色、左子節點任意顏色。一樣是間接借用兄弟節點的紅色右子節點補充到左子樹中,達到紅黑樹的平衡。
處理:替換節點的兄弟節點 S 設置成父節點P的顏色,兄弟節點的右子節點 SR 設置爲黑色,父節點P設置爲黑色,再對節點 P 左旋操做。此時節點R替換到刪除節點位置以後,紅黑樹從新達到平衡狀態。
場景 2.3:替換節點的兄弟節點是黑色且兄弟節點的左子節點是紅色,右子節點是黑色。
處理:替換節點的兄弟節點 S 設置成紅色,兄弟節點的左子節點 SL 設置爲黑色,再對節點 S 右旋操做,轉換到了場景 2.2,再進行場景 2.2 的操做。
場景 2.4:替換節點的兄弟節點的左右子節點都是黑色。兄弟節點的子節點不能借用,就只能借用兄弟節點了。
處理:替換節點的兄弟節點 S 設置成紅色,以父節點 P 看成替換節點,而後自底向上處理。
場景 3:替換節點是黑色節點、且是其父節點的右子節點。(與場景 2 鏡像)
場景 3.1:替換節點的兄弟節點是紅色。
處理:替換節點的父節點 P 設置紅色、兄弟節點 S 設置成黑色,再對節點 P 右旋操做,變成場景3.4。
場景 3.2:替換節點的兄弟節點是黑色且兄弟節點的左子節點是紅色、右子節點任意顏色。
處理:替換節點的兄弟節點 S 設置成父節點 P 的顏色,兄弟節點的左子節點 SL 設置爲黑色,父節點P 設置爲黑色,再對節點 P 右旋操做。此時節點 R 替換到刪除節點位置以後,紅黑樹從新達到平衡狀態。
場景 3.3:替換節點的兄弟節點是黑色且兄弟節點的右子節點是紅色、左子節點爲黑色。
處理:替換節點的兄弟節點 S 設置成紅色,兄弟節點的右子節點 SL 設置爲黑色,再對節點S左旋操做,轉換到了場景 3.2,再進行場景 3.2 的操做。
場景 3.4:替換節點的兄弟節點的左右子節點都是黑色。
處理:替換節點的兄弟節點 S 設置成紅色,以父節點 P 看成替換節點,而後自底向上處理。
節點刪除及平衡代碼:
/** * 查找節點 * @param key 節點key值 */ search(key) { let node = this.root while (node) { if (key < node.key) { node = node.left } else if (key > node.key) { node = node.right } else if (key === node.key) { break } } return node } /** * 替換u節點,重置v節點 * @param u 待刪除節點 * @param v 子節點 */ const replace = function(u, v) { if(!u.parent){ // u是根節點,設置v爲根節點 this.root = v } else if(u === u.parent.left){ // 重置u的父節點的左節點 u.parent.left = v } else { // 重置u的父節點的右節點 u.parent.right = v } // 重置v的父節點 v.parent = u.parent } /** * 查找node節點的後繼節點 */ findSuccessor(node) { while (node.left) { node = node.left; } return node; } /** * 刪除節點 * @param key 刪除節點key值 */ delete(key) { const node = search(key) if(!node){ return } let fix let color = node.color if(!node.left){ //左節點爲空值 fix = node.right this.replace(node, node.right) } else if(!node.right){ //右節點爲空值 fix = node.left this.replace(node, node.left) } else { // 左右節點都不爲空值 const successor = this.findSuccessor(node.right) //替換節點的顏色 color = successor.color //後繼節點只存在右節點或者兩個nil子節點狀況 fix = successor.right //若是後繼節點是父節點的非直接子節點 if(successor.parent !== node){ this.replace(successor, successor.right) successor.right = node.right successor.right.parent = successor } this.replace(node, successor) successor.color = node.color successor.left = node.left successor.left.parent = successor } if(color === Color.BLACK){ this.balanceDeletion(fix) } } /** * 刪除節點平衡修正 * @param node 節點 */ balanceDeletion(node) { while (node !== this.root && node.color === Color.BLACK) { // 節點是父節點的左子節點 if (node === node.parent.left) { //兄弟節點 let sibling = node.parent.right; if (sibling.color === Color.RED) { // 場景2.1:兄弟節點是紅色 // 兄弟節點設置爲黑色 sibling.color = Color.BLACK; //替換節點的父節點設置爲紅色 node.parent.color = Color.RED; // 左旋 this.rotateLeft(node.parent); sibling = node.parent.right; } if (sibling.left.color === Color.BLACK && sibling.right.color === Color.BLACK) { // 場景2.4: 兄弟節點兩個子節點都是黑色 sibling.color = Color.RED; //再次以父節點爲新節點做自平衡處理。 node = node.parent; continue; } else if (sibling.left.color === Color.RED) { // 場景2.3: 兄弟節點的左子節點是黑色,轉換到場景2.2. sibling.left.color = Color.BLACK; sibling.color = Color.RED; //對兄弟節點右旋 this.rotateRight(sibling) sibling = node.parent.right; } if (sibling.right.color === Color.RED) { //場景2.2:兄弟節點的右節點是紅色 sibling.color = node.parent.color; node.parent.color = Color.BLACK; sibling.right.color = Color.BLACK; //對父節點左旋 this.rotateLeft(node.parent); // 左旋以後,紅黑樹從新平衡 node = this.root; } } else { //節點是父節點的左節點 let sibling = node.parent.left; if (sibling.color === Color.RED) { // 場景 3.1:替換節點的史弟節點是紅色 sibling.color = Color.BLACK; node.parent.color = Color.RED; this.rotateRight(node.parent); sibling = node.parent.left; } if (sibling.right.color === Color.BLACK && sibling.left.color === Color.BLACK) { //場景3.4:替換節點的兩個子節點都是黑色 sibling.color = Color.RED; //再次以父節點爲新節點做自平衡處理。 node = node.parent; continue } else if (sibling.right.color === Color.RED) { // 場景3.3:兄弟節點的右子節點是紅色 sibling.right.color = Color.BLACK; sibling.color = Color.RED; this.rotateLeft(sibling); sibling = node.parent.left; } if (sibling.left.color === Color.RED) { // 場景3.2:兄弟節點的左子節點是紅色 sibling.color = node.parent.color; node.parent.color = Color.BLACK; sibling.left.color = Color.BLACK; this.rotateRight(node.parent); node = this.root; } } } node.color = Color.BLACK; } }
紅黑樹普遍用在 Java 的集合框架 (HashMap、TreeMap、TreeSet)、Nginx 的 Timer 管理、Linux 虛擬內存管理以及 C++ 的 STL 等等場景。
在 Linux 內核中,每一個用戶進程均可以訪問 4GB 的線性虛擬空間,虛擬空間每每須要多個虛擬內存區域描述,對這些內存區域,Linux 內核採用了鏈表以及紅黑樹形式組織。內存區域按地址排序,連接成一個鏈表以及一顆紅黑樹,尋找空閒區域時只須要遍歷這個鏈表,在發生缺頁中斷時經過紅黑樹快速檢索特定內存區域。
紅黑樹的刪除操做就基本介紹完了,總結一下刪除操做就是,刪除節點等同於刪除替換節點,若替換節點是紅色節點時,直接刪除不會影響平衡;若替換節點是黑色節點時,就須要借用兄弟節點的右子節點、左子節點或者兄弟節點。
紅黑樹最吸引人的是它的全部操做在最差狀況下能夠保證 O(logN) 的時間複雜度,穩定且高效。例如要在10 萬條(2^20)數據中查找一條數據,只須要 20 次的操做就能完成。但這些保證有一個前置條件,就是數據量不大,且數據能夠徹底放到內存中。在數據量比較大時,由於紅黑樹的深度比較大形成磁盤 IO 的頻繁讀寫,會致使它的效率低下。
另外推薦 Data Structure Visualizations 網站,它包含很是多的數據結構方面的可視化算法題。其中就有 紅黑樹的算法,對照着在線生成的紅黑樹看,會更容易理解紅黑樹中各類操做場景。
政採雲前端團隊(ZooTeam),一個年輕富有激情和創造力的前端團隊,隸屬於政採雲產品研發部,Base 在風景如畫的杭州。團隊現有 50 餘個前端小夥伴,平均年齡 27 歲,近 3 成是全棧工程師,妥妥的青年風暴團。成員構成既有來自於阿里、網易的「老」兵,也有浙大、中科大、杭電等校的應屆新人。團隊在平常的業務對接以外,還在物料體系、工程平臺、搭建平臺、性能體驗、雲端應用、數據分析及可視化等方向進行技術探索和實戰,推進並落地了一系列的內部技術產品,持續探索前端技術體系的新邊界。
若是你想改變一直被事折騰,但願開始能折騰事;若是你想改變一直被告誡須要多些想法,卻無從破局;若是你想改變你有能力去作成那個結果,卻不須要你;若是你想改變你想作成的事須要一個團隊去支撐,但沒你帶人的位置;若是你想改變既定的節奏,將會是「5 年工做時間 3 年工做經驗」;若是你想改變原本悟性不錯,但老是有那一層窗戶紙的模糊… 若是你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的本身。若是你但願參與到隨着業務騰飛的過程,親手推進一個有着深刻的業務理解、完善的技術體系、技術創造價值、影響力外溢的前端團隊的成長曆程,我以爲咱們該聊聊。任什麼時候間,等着你寫點什麼,發給 ZooTeam@cai-inc.com