摘要:紅黑樹的刪除可能出現(xiàn)的情形討論刪除紅黑樹中一個結(jié)點,刪除的結(jié)點是其子結(jié)點狀態(tài)和顏色的組合。組合被刪結(jié)點無子結(jié)點,且被刪結(jié)點為紅色此時直接將結(jié)點刪除即可,不破壞任何紅黑樹的性質(zhì)。
紅黑樹的刪除 可能出現(xiàn)的情形討論
刪除紅黑樹中一個結(jié)點,刪除的結(jié)點是其子結(jié)點狀態(tài)和顏色的組合。子結(jié)點的狀態(tài)有三種:無子結(jié)點、只有一個子結(jié)點、有兩個子結(jié)點。顏色有紅色和黑色兩種。所以共會有6種組合。
組合1:被刪結(jié)點無子結(jié)點,且被刪結(jié)點為紅色此時直接將結(jié)點刪除即可,不破壞任何紅黑樹的性質(zhì)。
組合2:被刪結(jié)點無子結(jié)點,且被刪結(jié)點為黑色處理方法略微復(fù)雜,稍后再議。
組合3:被刪結(jié)點有一個子結(jié)點,且被刪結(jié)點為紅色這種組合是不存在的,如圖假如被刪結(jié)點node只有一個有值的子結(jié)點value,而以value為根結(jié)點的子樹中,必然還存在null結(jié)點,如此不符合紅黑樹的性質(zhì)5,對每個結(jié)點,從該結(jié)點到其所有后代葉結(jié)點的簡單路徑上,均包含相同數(shù)目的黑色結(jié)點。
組合4:被刪結(jié)點有一個子結(jié)點,且被刪結(jié)點為黑色這種組合下,被刪結(jié)點node的另一個子結(jié)點value必然為紅色,此時直接將node刪掉,用value代替node的位置,并將value著黑即可。
組合5&6:被刪結(jié)點有兩個子結(jié)點,且被刪結(jié)點為黑色或紅色當被刪結(jié)點node有兩個子結(jié)點時,先要找到這個被刪結(jié)點的后繼結(jié)點successor,然后用successor代替node的位置,同時著成node的顏色,此時相當于successor被刪。
因為node有兩個子結(jié)點,所以successor必然在node的右子樹中,必然是下圖兩種形態(tài)中的一種。
若是(a)的情形,用successor代替node后,相當于successor被刪,若successor為紅色,則變成了組合1;若successor為黑色,則變成了組合2。
若是(b)的情形,用successor代替node后,相當于successor被刪,若successor為紅色,則變成了組合1;若successor為黑色,則變成了組合2或4。
綜上若被刪結(jié)點是組合1或組合4的狀態(tài),很容易處理;被刪結(jié)點不可能是組合3的狀態(tài);被刪結(jié)點是組合5&6的狀態(tài),將變成組合1或組合2或組合4。
再議組合2:被刪結(jié)點無子結(jié)點,且被刪結(jié)點為黑色因為刪除黑色結(jié)點會破壞紅黑樹的性質(zhì)5,所以為了不破壞性質(zhì)5,在替代結(jié)點上額外增加一個黑色,這樣不違背性質(zhì)5而只違背性質(zhì)1,每個結(jié)點或是黑色或是紅色。此時將額外的黑色移除,則完成刪除操作。
然后再結(jié)合node原來的父結(jié)點father和其兄弟結(jié)點brother來分析。
情形一brother為黑色,且brother有一個與其方向一致的紅色子結(jié)點son,所謂方向一致,是指brother為father的左子結(jié)點,son也為brother的左子結(jié)點;或者brother為father的右子結(jié)點,son也為brother的右子結(jié)點。
圖(c)中,白色代表隨便是黑或是紅,方形結(jié)點除了存儲自身黑色外,還額外存儲一個黑色。將brother和father旋轉(zhuǎn),并重新上色后,變成了圖(d),方形結(jié)點額外存儲的黑色轉(zhuǎn)移到了father,且不違背任何紅黑樹的性質(zhì),刪除操作完成。
圖(c)中的情形顛倒過來,也是一樣的操作。
情形二brother為黑色,且brother有一個與其方向不一致的紅色子結(jié)點son
圖(e)中,將son和brother旋轉(zhuǎn),重新上色后,變成了圖(f),情形一。
圖(e)中的情形顛倒過來,也是一樣的操作。
情形三brother為黑色,且brother無紅色子結(jié)點
此時若father為紅,則重新著色即可,刪除操作完成。如圖下圖(g)和(h)。
此時若father為黑,則重新著色,將額外的黑色存到father,將father作為新的結(jié)點進行情形判斷,遇到情形一、情形二,則進行相應(yīng)的調(diào)整,完成刪除操作;如果沒有,則結(jié)點一直上移,直到根結(jié)點存儲額外的黑色,此時將該額外的黑色移除,即完成了刪除操作。
情形四brother為紅色,則father必為黑色。
圖(i)中,將brother和father旋轉(zhuǎn),重新上色后,變成了圖(j),新的brother變成了黑色,這樣就成了情形一、二、三中的一種。如果將son和brother旋轉(zhuǎn),無論怎么重新上色,都會破壞紅黑樹的性質(zhì)4或5,例如圖(k)。
圖(i)中的情形顛倒過來,也是一樣的操作。
// 結(jié)點 function Node(value) { this.value = value this.color = "red" // 結(jié)點的顏色默認為紅色 this.parent = null this.left = null this.right = null } function RedBlackTree() { this.root = null } RedBlackTree.prototype.insert = function (node) { // 以二叉搜索樹的方式插入結(jié)點 // 如果根結(jié)點不存在,則結(jié)點作為根結(jié)點 // 如果結(jié)點的值小于node,且結(jié)點的右子結(jié)點不存在,跳出循環(huán) // 如果結(jié)點的值大于等于node,且結(jié)點的左子結(jié)點不存在,跳出循環(huán) if (!this.root) { this.root = node } else { let current = this.root while (current[node.value <= current.value ? "left" : "right"]) { current = current[node.value <= current.value ? "left" : "right"] } current[node.value <= current.value ? "left" : "right"] = node node.parent = current } // 判斷情形 this._fixTree(node) return this } RedBlackTree.prototype._fixTree = function (node) { // 當node.parent不存在時,即為情形1,跳出循環(huán) // 當node.parent.color === "black"時,即為情形2,跳出循環(huán) while (node.parent && node.parent.color !== "black") { // 情形3 let father = node.parent let grand = father.parent let uncle = grand[grand.left === father ? "right" : "left"] if (!uncle || uncle.color === "black") { // 葉結(jié)點也是黑色的 // 情形3.1 let directionFromFatherToNode = father.left === node ? "left" : "right" let directionFromGrandToFather = grand.left === father ? "left" : "right" if (directionFromFatherToNode === directionFromGrandToFather) { // 具體情形一或二 // 旋轉(zhuǎn) this._rotate(father) // 變色 father.color = "black" grand.color = "red" } else { // 具體情形三或四 // 旋轉(zhuǎn) this._rotate(node) this._rotate(node) // 變色 node.color = "black" grand.color = "red" } break // 完成插入,跳出循環(huán) } else { // 情形3.2 // 變色 grand.color = "red" father.color = "black" uncle.color = "black" // 將grand設(shè)為新的node node = grand } } if (!node.parent) { // 如果是情形1 node.color = "black" this.root = node } } RedBlackTree.prototype._rotate = function (node) { // 旋轉(zhuǎn) node 和 node.parent let y = node.parent if (y.right === node) { if (y.parent) { y.parent[y.parent.left === y ? "left" : "right"] = node } node.parent = y.parent if (node.left) { node.left.parent = y } y.right = node.left node.left = y y.parent = node } else { if (y.parent) { y.parent[y.parent.left === y ? "left" : "right"] = node } node.parent = y.parent if (node.right) { node.right.parent = y } y.left = node.right node.right = y y.parent = node } } RedBlackTree.prototype.remove = function (node) { while (true) { let { left, right, parent, color } = node // 組合1 if (!left && !right && color === "red") { parent[parent.left === node ? "left" : "right"] = null return this } // 組合2 if (!left && !right && color === "black") { if (parent) { let nullNode = new Node(null) nullNode.parent = parent nullNode.color = ["black", "black"] parent[parent.left === node ? "left" : "right"] = nullNode this._repairTree(nullNode) } else { this.root = null } return this } // 組合4 if ((!left && right && color === "black") || (left && !right && color === "black")) { if (parent) { parent[parent.left === node ? "left" : "right"] = node.left || node.right } else { this.root = node.left || node.right } node[node.left ? "left" : "right"].color = "black" return this } // 組合5&6 if (left && right) { // 尋找后繼結(jié)點 let successor = right while (successor.left) { successor = successor.left } // 用后繼結(jié)點代替node node.value = successor.value // 刪除后街結(jié)點 node = successor /* let successorColor = successor.color let successorLeft = successor.left let successorRight = successor.right let successorParent = successor.parent // 用后繼節(jié)點代替node if (parent) { parent[parent.left === node ? "left" : "right"] = successor } else { this.root = successor } successor.parent = parent successor.left = left successor.right = right left.parent = successor right.parent = successor successor.color = color // 刪除successor node.left = successorLeft node.right = successorRight node.parent = successorParent node.color = successorColor */ } } } RedBlackTree.prototype._repairTree = function (node) { while (node.parent) { let father = node.parent let brother = father[father.left === node ? "right" : "left"] let son = brother[father.left === node ? "right" : "left"] let daugh = brother[father.left === node ? "left" : "right"] if (brother.color === "black") { if (son && son.color === "red") { // 情形一 // 旋轉(zhuǎn)brother和father this._rotate(brother) // 變色 brother.color = father.color father.color = "black" son.color = "black" // 移除black if (!node.value) { // nullNode father[father.left === node ? "left" : "right"] = null } else { node.color = "black" } // 刪除操作完成 return } else if (daugh && daugh.color === "red") { // 情形二 // 旋轉(zhuǎn)son和brother this._rotate(son) // 變色 son.color = "black" brother.color = "red" // 變成情形一,繼續(xù)循環(huán) } else { // 情形三 // brother無紅子結(jié)點 if (father.color === "red") { // father為紅色 father.color = "black" brother.color = "red" // 移除black if (!node.value) { // nullNode father[father.left === node ? "left" : "right"] = null } else { node.color = "black" } // 刪除操作完成 return } else { // father為黑色 father.color = ["black", "black"] brother.color = "red" // 移除black if (!node.value) { // nullNode father[father.left === node ? "left" : "right"] = null } else { node.color = "black" } node = father // 結(jié)點上移,繼續(xù)循環(huán) } } } else { // 情形四 this._rotate(brother) brother.color = "black" father.color = "red" // 繼續(xù)循環(huán) } } this.root = node node.color = "black" } RedBlackTree.prototype.find = function (value) { let current = this.root while (current.value !== value) { current = current[value >= current.value ? "right" : "left"] } return current } let arr = [11, 2, 14, 1, 7, 15, 5, 8, 4] let tree = new RedBlackTree() arr.forEach(i => tree.insert(new Node(i))) let findNode = tree.find(15) tree.remove(findNode) debugger
紅黑樹的插入
一點感悟紅黑樹的插入和刪除都是通過分類討論來解決的,耐心的分析即可。
為數(shù)不多使用技巧的地方,是為了維持紅黑樹的性質(zhì),在結(jié)點上存兩個黑色,當然這是算法導(dǎo)論告訴我的。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/89936.html
摘要:很多文章或書籍在介紹紅黑樹的時候直接上來就是紅黑樹的個基本性質(zhì)插入刪除操作等。這也不奇怪,算法的作者就是紅黑樹的作者之一。所以,理解樹對掌握紅黑樹是至關(guān)重要的。 本文主要包括以下內(nèi)容: 什么是2-3樹 2-3樹的插入操作 紅黑樹與2-3樹的等價關(guān)系 《算法4》和《算法導(dǎo)論》上關(guān)于紅黑樹的差異 紅黑樹的5條基本性質(zhì)的分析 紅黑樹與2-3-4樹的等價關(guān)系 紅黑樹的插入、刪除操作 JDK ...
摘要:需要執(zhí)行的操作依次是首先,將紅黑樹當作一顆二叉查找樹,將該節(jié)點從二叉查找樹中刪除然后,通過旋轉(zhuǎn)和重新著色等一系列來修正該樹,使之重新成為一棵紅黑樹。 雖是讀書筆記,但是如轉(zhuǎn)載請注明出處 http://segmentfault.com/blog/exploring/ .. 拒絕伸手復(fù)制黨 關(guān)于二叉樹的基本知識,可以參見:Java 實現(xiàn)基本數(shù)據(jù)結(jié)構(gòu) 2(樹) 以下是算法導(dǎo)論第13章的學(xué)...
摘要:強調(diào)一下,紅黑樹中的葉子節(jié)點指的都是節(jié)點。故刪除之后紅黑樹平衡不用調(diào)整。將達到紅黑樹平衡。到此關(guān)于紅黑樹的基礎(chǔ)已經(jīng)介紹完畢,下一章我將就源碼中的進行講解說明,看一看紅黑樹是如何在源碼中實現(xiàn)的。 說到HashMap,就一定要說到紅黑樹,紅黑樹作為一種自平衡二叉查找樹,是一種用途較廣的數(shù)據(jù)結(jié)構(gòu),在jdk1.8中使用紅黑樹提升HashMap的性能,今天就來說一說紅黑樹,上一講已經(jīng)給出插入平衡...
摘要:用這種范例表示紅黑樹是可能的,但是這會改變一些性質(zhì)并使算法復(fù)雜。插入會出現(xiàn)種情況為根節(jié)點,即紅黑樹的根節(jié)點。 說到HashMap,就一定要說到紅黑樹,紅黑樹作為一種自平衡二叉查找樹,是一種用途較廣的數(shù)據(jù)結(jié)構(gòu),在jdk1.8中使用紅黑樹提升HashMap的性能,今天就來說一說紅黑樹。 前言 限于篇幅,本文只對紅黑樹的基礎(chǔ)進行說明,暫不涉及源碼部分,大部分摘抄自維基百科,這里也貼出對應(yīng)鏈接...
摘要:在上一節(jié)中,在中用了鏈表和紅黑樹兩種方式解決沖突,在中也是用紅黑樹存儲的。其中節(jié)點顏色為黑色紅黑樹的左旋和右旋紅黑樹的插入和刪除,都有可能破壞其特性,就不是一棵紅黑樹了,所以要調(diào)整。 在上一節(jié)中,HashMap在jdk 1.8中用了鏈表和紅黑樹兩種方式解決沖突,在TreeMap中也是用紅黑樹存儲的。下面分析一下紅黑樹的結(jié)構(gòu)和基本操作。 一、紅黑樹的特征和基本操作 上一節(jié)中已經(jīng)描述了紅黑...
閱讀 1261·2023-04-26 02:38
閱讀 944·2023-04-25 20:13
閱讀 3599·2021-11-19 11:31
閱讀 2403·2019-08-30 15:55
閱讀 2730·2019-08-30 14:11
閱讀 3170·2019-08-30 13:45
閱讀 1385·2019-08-29 18:41
閱讀 1158·2019-08-29 16:18