數(shù)據(jù)結(jié)構(gòu):紅黑樹實(shí)現(xiàn)原理,從0基礎(chǔ)解釋到底層代碼實(shí)現(xiàn)手寫
什么是紅黑樹?
紅黑樹是一種自平衡的二叉查找樹,是一種高效的查找樹。它是由 Rudolf Bayer 于1972年發(fā)明,在當(dāng)時(shí)被稱為對(duì)稱二叉 B 樹(symmetric binary B-trees)。后來,在1978年被 Leo J. Guibas 和 Robert Sedgewick 修改為如今的紅黑樹。紅黑樹具有良好的效率,它可在 O (logN) 時(shí)間內(nèi)完成查找、增加、刪除等操作。因此,紅黑樹在業(yè)界應(yīng)用很廣泛,比如 Java 中的 TreeMap,JDK 1.8中的 HashMap、C++ STL 中的 map 均是基于紅黑樹結(jié)構(gòu)實(shí)現(xiàn)的。
簡(jiǎn)單介紹一下什么是O(logN)
當(dāng)我們談?wù)撍惴ǖ男蕰r(shí),我們通常使用時(shí)間復(fù)雜度來描述算法的運(yùn)行時(shí)間與輸入規(guī)模之間的關(guān)系。時(shí)間復(fù)雜度以大O符號(hào)(O)表示。
時(shí)間復(fù)雜度 O(logN)
在時(shí)間復(fù)雜度中,O(logN) 是一種非常高效的情況。它表示算法的運(yùn)行時(shí)間與輸入規(guī)模的對(duì)數(shù)成正比。這意味著隨著輸入規(guī)模的增加,算法的運(yùn)行時(shí)間將以對(duì)數(shù)的速度增長。
具體來說,假設(shè)一個(gè)算法的時(shí)間復(fù)雜度是 O(logN),其中 N 代表輸入規(guī)模。當(dāng) N 增加時(shí),該算法的運(yùn)行時(shí)間將以對(duì)數(shù)的速度增加。這意味著隨著輸入規(guī)模的翻倍,該算法的運(yùn)行時(shí)間僅會(huì)略微增加。
O(logN) 的高效性來自于對(duì)數(shù)函數(shù)的特性。對(duì)數(shù)函數(shù)具有一個(gè)很快的增長速度,但在開始時(shí)增長很快,隨著輸入的增加,增長速度逐漸減緩。這使得算法能夠在處理大規(guī)模輸入時(shí)保持相對(duì)較低的運(yùn)行時(shí)間。
紅黑樹的基本概念(應(yīng)付面試官)
紅黑樹是一種特殊的二叉查找樹,它除了滿足二叉查找樹的基本性質(zhì)外,還具有以下幾個(gè)特點(diǎn):
- 每個(gè)節(jié)點(diǎn)都有一個(gè)顏色屬性,要么是紅色,要么是黑色。
- 根節(jié)點(diǎn)的顏色是黑色。
- 所有葉子節(jié)點(diǎn)(NIL節(jié)點(diǎn))的顏色都是黑色。
- 如果一個(gè)節(jié)點(diǎn)是紅色,那么它的兩個(gè)子節(jié)點(diǎn)都是黑色。
- 從任意一個(gè)節(jié)點(diǎn)到其所有后代葉子節(jié)點(diǎn)的簡(jiǎn)單路徑上,包含相同數(shù)量的黑色節(jié)點(diǎn)。
這些性質(zhì)保證了紅黑樹在插入和刪除操作后能夠自動(dòng)調(diào)整,使得樹保持平衡狀態(tài)。平衡狀態(tài)指的是從根節(jié)點(diǎn)到葉子節(jié)點(diǎn)的最長路徑不超過最短路徑的兩倍。這樣就避免了二叉查找樹在極端情況下退化成鏈表,導(dǎo)致效率降低。
紅黑樹的性質(zhì)
為了方便分析,我們定義以下幾個(gè)概念:
- 黑高(black-height):從某個(gè)節(jié)點(diǎn)出發(fā)(不包括該節(jié)點(diǎn))到達(dá)一個(gè)葉子節(jié)點(diǎn)的任意一條路徑上,黑色節(jié)點(diǎn)的個(gè)數(shù)稱為該節(jié)點(diǎn)的黑高,記為 bh(x)。
- 紅黑性質(zhì)(red-black property):指紅黑樹滿足上述五個(gè)性質(zhì)。
- 紅黑樹(red-black tree):一棵滿足紅黑性質(zhì)的二叉查找樹。
有了這些定義,我們可以推導(dǎo)出以下幾個(gè)重要的性質(zhì):
- 性質(zhì)1:一棵有 n 個(gè)內(nèi)部節(jié)點(diǎn)(非 NIL 節(jié)點(diǎn))的紅黑樹,其高度 h 不超過 2log(n+1)。證明:設(shè) x 是紅黑樹中任意一個(gè)內(nèi)部節(jié)點(diǎn),h(x) 是以 x 為根的子樹的高度,n(x) 是以 x 為根的子樹內(nèi)部節(jié)點(diǎn)的個(gè)數(shù)。由于 x 的兩個(gè)子節(jié)點(diǎn)至少有一個(gè)是黑色(性質(zhì)4),所以 h(x) >= bh(x)。另一方面,由于從 x 到其后代葉子節(jié)點(diǎn)的任意路徑上至少有一半是黑色(性質(zhì)5),所以 bh(x) >= h(x)/2。綜合起來,有 h(x) >= bh(x) >= h(x)/2。由此可得 h(x) <= 2bh(x)。對(duì)于整棵樹而言,設(shè)其高度為 h,內(nèi)部節(jié)點(diǎn)個(gè)數(shù)為 n,則有 h <= 2bh(root)。由于 root 是黑色(性質(zhì)2),所以 bh(root) >= 1。同時(shí),由于每條從 root 到葉子節(jié)點(diǎn)路徑上至少包含 bh(root) 個(gè)黑色節(jié)點(diǎn),所以 n >= 2^bh(root) - 1。綜合起來,有 h <= 2bh(root) <= 2log(n+1)。
- 性質(zhì)2:在一棵紅黑樹中,從根節(jié)點(diǎn)到葉子節(jié)點(diǎn)的所有路徑中,最長的路徑不超過最短路徑的兩倍。證明:設(shè) h 是紅黑樹的高度,bh 是紅黑樹的黑高。由于最短路徑上全部是黑色節(jié)點(diǎn),所以最短路徑長度為 bh。由于最長路徑上紅色節(jié)點(diǎn)和黑色節(jié)點(diǎn)交替出現(xiàn)(性質(zhì)4),所以最長路徑長度為 2bh。因此,最長路徑長度不超過最短路徑長度的兩倍。
- 性質(zhì)3:對(duì)于一棵有 n 個(gè)內(nèi)部節(jié)點(diǎn)的紅黑樹,其黑高 bh 滿足不等式 bh >= log(n+1)/2。證明:由性質(zhì)1可得 h <= 2log(n+1),又由于 h >= bh,所以 bh <= log(n+1)/2。紅黑樹的操作
紅黑樹的操作
紅黑樹的基本操作包括查找、插入和刪除。查找操作和普通的二叉查找樹一樣,不需要特殊處理。插入和刪除操作則需要考慮如何維護(hù)紅黑性質(zhì),避免樹失去平衡。為了實(shí)現(xiàn)這一目的,我們需要使用兩種基本操作:顏色變換和旋轉(zhuǎn)。
顏色變換
顏色變換是指將某個(gè)節(jié)點(diǎn)的顏色從紅色變?yōu)楹谏蛘邚暮谏優(yōu)榧t色。這個(gè)操作很簡(jiǎn)單,只需要修改節(jié)點(diǎn)的顏色屬性即可。但是,顏色變換會(huì)影響紅黑性質(zhì),尤其是性質(zhì)4和性質(zhì)5。因此,在進(jìn)行顏色變換時(shí),需要注意以下幾點(diǎn):
- 顏色變換不能作用于根節(jié)點(diǎn),否則會(huì)違反性質(zhì)2(根是黑色)。
- 顏色變換不能作用于 NIL 節(jié)點(diǎn),否則會(huì)違反性質(zhì)3(所有葉子都是黑色)。
- 顏色變換不能使一個(gè)節(jié)點(diǎn)從紅色變?yōu)榧t色,或者從黑色變?yōu)楹谏駝t沒有意義。
- 顏色變換不能使一個(gè)節(jié)點(diǎn)和其父節(jié)點(diǎn)或子節(jié)點(diǎn)同為紅色,否則會(huì)違反性質(zhì)4(每個(gè)紅色節(jié)點(diǎn)必須有兩個(gè)黑色的子節(jié)點(diǎn))。
旋轉(zhuǎn)
旋轉(zhuǎn)是指將某個(gè)節(jié)點(diǎn)沿著其父子關(guān)系向上或向下移動(dòng)的操作。旋轉(zhuǎn)分為左旋和右旋兩種,左旋是將某個(gè)節(jié)點(diǎn)向上提升為其右孩子的父親,右旋是將某個(gè)節(jié)點(diǎn)向下降低為其左孩子的孩子。旋轉(zhuǎn)操作可以保持二叉查找樹的性質(zhì)不變,但會(huì)改變樹的形狀和高度。旋轉(zhuǎn)操作可以用來調(diào)整樹的平衡狀態(tài),使之更加接近理想的情況。
下圖展示了左旋和右旋的示意圖:
左旋
右旋
左旋操作可以描述如下:
- 設(shè) x 是要進(jìn)行左旋的節(jié)點(diǎn),y 是 x 的右孩子。
- 將 y 的左孩子設(shè)為 x 的右孩子,并將 y 的左孩子的父親設(shè)為 x。
- 將 x 的父親設(shè)為 y 的父親,并更新 y 的父親的相應(yīng)孩子指針。
- 將 y 的左孩子設(shè)為 x,并將 x 的父親設(shè)為 y。
右旋操作與左旋類似。
紅黑樹的插入
紅黑樹的插入操作是在二叉查找樹的基礎(chǔ)上進(jìn)行的,即先按照二叉查找樹的規(guī)則找到合適的位置插入新節(jié)點(diǎn),然后再調(diào)整樹的結(jié)構(gòu)和顏色,使之恢復(fù)紅黑性質(zhì)。插入操作的具體步驟如下:
- 創(chuàng)建一個(gè)新節(jié)點(diǎn) z,并將其顏色設(shè)為紅色。
- 按照二叉查找樹的規(guī)則,將 z 插入到合適的位置,并將其父節(jié)點(diǎn)設(shè)為 y。
- 如果 y 是 NIL 或者黑色,那么不需要做任何調(diào)整,直接返回。
- 如果 y 是紅色,那么就存在雙紅問題,即 z 和 y 都是紅色,違反了性質(zhì)4。此時(shí),需要根據(jù) y 的叔叔節(jié)點(diǎn)(y 的父節(jié)點(diǎn)的兄弟節(jié)點(diǎn))的顏色進(jìn)行不同的處理:如果 y 的叔叔節(jié)點(diǎn)是紅色,那么將 y 和其叔叔節(jié)點(diǎn)的顏色都變?yōu)楹谏?,?y 的父節(jié)點(diǎn)的顏色變?yōu)榧t色,并將 y 的父節(jié)點(diǎn)作為新的 z 節(jié)點(diǎn),繼續(xù)向上調(diào)整。如果 y 的叔叔節(jié)點(diǎn)是黑色或 NIL,那么需要進(jìn)行旋轉(zhuǎn)操作。具體來說,分為以下四種情況:如果 z 是 y 的右孩子,并且 y 是其父節(jié)點(diǎn)的左孩子,那么對(duì) y 進(jìn)行左旋,然后交換 z 和 y 的角色。如果 z 是 y 的左孩子,并且 y 是其父節(jié)點(diǎn)的右孩子,那么對(duì) y 進(jìn)行右旋,然后交換 z 和 y 的角色。如果 z 是 y 的左孩子,并且 y 是其父節(jié)點(diǎn)的左孩子,那么對(duì) y 的父節(jié)點(diǎn)進(jìn)行右旋,然后將 y 的顏色變?yōu)楹谏?,?y 的父節(jié)點(diǎn)(原來的祖父節(jié)點(diǎn))的顏色變?yōu)榧t色,并結(jié)束調(diào)整。如果 z 是 y 的右孩子,并且 y 是其父節(jié)點(diǎn)的右孩子,那么對(duì) y 的父節(jié)點(diǎn)進(jìn)行左旋,然后將 y 的顏色變?yōu)楹谏?,?y 的父節(jié)點(diǎn)(原來的祖父節(jié)點(diǎn))的顏色變?yōu)榧t色,并結(jié)束調(diào)整。
下圖展示了插入操作的示意圖:
紅黑樹的刪除
紅黑樹的刪除操作也是在二叉查找樹的基礎(chǔ)上進(jìn)行的,即先按照二叉查找樹的規(guī)則找到要?jiǎng)h除的節(jié)點(diǎn),并用其后繼節(jié)點(diǎn)或前驅(qū)節(jié)點(diǎn)替換它(如果存在),然后再調(diào)整樹的結(jié)構(gòu)和顏色,使之恢復(fù)紅黑性質(zhì)。刪除操作的具體步驟如下:
- 找到要?jiǎng)h除的節(jié)點(diǎn) z,并用其后繼節(jié)點(diǎn)或前驅(qū)節(jié)點(diǎn)(如果存在)替換它。設(shè)替換后的新節(jié)點(diǎn)為 x。
- 如果 x 和 z 中至少有一個(gè)是紅色,那么不需要做任何調(diào)整,直接返回。
- 如果 x 和 z 都是黑色,那么就存在過度黑問題,即 x 多了一個(gè)額外的黑色屬性(因?yàn)樘鎿Q了 z),導(dǎo)致違反了性質(zhì)5。此時(shí),需要根據(jù) x 的兄弟節(jié)點(diǎn)(x 的父節(jié)點(diǎn)的另一個(gè)孩子)的顏色進(jìn)行不同的處理:如果 x 的兄弟節(jié)點(diǎn)是紅色,那么將 x 的兄弟節(jié)點(diǎn)的顏色變?yōu)楹谏瑢?x 的父節(jié)點(diǎn)的顏色變?yōu)榧t色,并對(duì) x 的父節(jié)點(diǎn)進(jìn)行左旋(如果 x 是左孩子)或右旋(如果 x 是右孩子),然后更新 x 的兄弟節(jié)點(diǎn)。如果 x 的兄弟節(jié)點(diǎn)是黑色,那么分為以下四種情況:如果 x 的兄弟節(jié)點(diǎn)的兩個(gè)孩子都是黑色或 NIL,那么將 x 的兄弟節(jié)點(diǎn)的顏色變?yōu)榧t色,并將 x 的父節(jié)點(diǎn)作為新的 x 節(jié)點(diǎn),繼續(xù)向上調(diào)整。如果 x 是左孩子,并且 x 的兄弟節(jié)點(diǎn)的左孩子是紅色,右孩子是黑色或 NIL,那么將 x 的兄弟節(jié)點(diǎn)的顏色變?yōu)榧t色,將 x 的兄弟節(jié)點(diǎn)的左孩子的顏色變?yōu)楹谏?,并?duì) x 的兄弟節(jié)點(diǎn)進(jìn)行右旋,然后更新 x 的兄弟節(jié)點(diǎn)。如果 x 是右孩子,并且 x 的兄弟節(jié)點(diǎn)的右孩子是紅色,左孩子是黑色或 NIL,那么將 x 的兄弟節(jié)點(diǎn)的顏色變?yōu)榧t色,將 x 的兄弟節(jié)點(diǎn)的右孩子的顏色變?yōu)楹谏?,并?duì) x 的兄弟節(jié)點(diǎn)進(jìn)行左旋,然后更新 x 的兄弟節(jié)點(diǎn)。如果 x 是左孩子,并且 x 的兄弟節(jié)點(diǎn)的右孩子是紅色,那么將 x 的父節(jié)點(diǎn)的顏色賦給 x 的兄弟節(jié)點(diǎn),將 x 的父節(jié)點(diǎn)和 x 的兄弟節(jié)點(diǎn)的右孩子的顏色都變?yōu)楹谏?duì) x 的父節(jié)點(diǎn)進(jìn)行左旋,然后結(jié)束調(diào)整。如果 x 是右孩子,并且 x 的兄弟節(jié)點(diǎn)的左孩子是紅色,那么將 x 的父節(jié)點(diǎn)的顏色賦給 x 的兄弟節(jié)點(diǎn),將 x 的父節(jié)點(diǎn)和 x 的兄弟節(jié)點(diǎn)的左孩子的顏色都變?yōu)楹谏?,并?duì) x 的父節(jié)點(diǎn)進(jìn)行右旋,然后結(jié)束調(diào)整。
紅黑樹的實(shí)現(xiàn)
下面給出了用 java語言實(shí)現(xiàn)紅黑樹的部分代碼。首先定義了一個(gè) Node 類,表示樹中的每個(gè)節(jié)點(diǎn)。每個(gè)節(jié)點(diǎn)有五個(gè)屬性:key(鍵值),color(顏色),left(左孩子),right(右孩子)和 parent(父親)。其中 color 用 0 表示黑色,用 1 表示紅色。NIL 節(jié)點(diǎn)用 None 表示。
// 定義一個(gè) Node 類,表示樹中的每個(gè)節(jié)點(diǎn)
class Node {
int key; // 鍵值
int color; // 顏色,0 表示黑色,1 表示紅色
Node left; // 左孩子
Node right; // 右孩子
Node parent; // 父親
// 構(gòu)造方法,初始化鍵值和顏色
public Node(int key, int color) {
this.key = key;
this.color = color;
this.left = null;
this.right = null;
this.parent = null;
}
}
// 定義一個(gè) RBTree 類,表示一棵紅黑樹
class RBTree {
Node root; // 根節(jié)點(diǎn)
// 構(gòu)造方法,初始化根節(jié)點(diǎn)為 null
public RBTree() {
this.root = null;
}
// 輔助方法,對(duì)某個(gè)節(jié)點(diǎn)進(jìn)行左旋操作
public void leftRotate(Node x) {
// 設(shè) y 是 x 的右孩子
Node y = x.right;
// 將 y 的左孩子設(shè)為 x 的右孩子,并將 y 的左孩子的父親設(shè)為 x
x.right = y.left;
if (y.left != null) {
y.left.parent = x;
}
// 將 x 的父親設(shè)為 y 的父親,并更新 y 的父親的相應(yīng)孩子指針
y.parent = x.parent;
if (x.parent == null) { // 如果 x 是根節(jié)點(diǎn),那么將 y 設(shè)為新的根節(jié)點(diǎn)
this.root = y;
} else if (x == x.parent.left) { // 如果 x 是其父節(jié)點(diǎn)的左孩子,那么將 y 設(shè)為其父節(jié)點(diǎn)的左孩子
x.parent.left = y;
} else { // 如果 x 是其父節(jié)點(diǎn)的右孩子,那么將 y 設(shè)為其父節(jié)點(diǎn)的右孩子
x.parent.right = y;
}
// 將 y 的左孩子設(shè)為 x,并將 x 的父親設(shè)為 y
y.left = x;
x.parent = y;
}
// 輔助方法,對(duì)某個(gè)節(jié)點(diǎn)進(jìn)行右旋操作
public void rightRotate(Node x) {
// 設(shè) y 是 x 的左孩子
Node y = x.left;
// 將 y 的右孩子設(shè)為 x 的左孩子,并將 y 的右孩子的父親設(shè)為 x
x.left = y.right;
if (y.right != null) {
y.right.parent = x;
}
// 將 x 的父親設(shè)為 y 的父親,并更新 y 的父親的相應(yīng)孩子指針
y.parent = x.parent;
if (x.parent == null) { // 如果 x 是根節(jié)點(diǎn),那么將 y 設(shè)為新的根節(jié)點(diǎn)
this.root = y;
} else if (x == x.parent.right) { // 如果 x 是其父節(jié)點(diǎn)的右孩子,那么將 y 設(shè)為其父節(jié)點(diǎn)的右孩子
x.parent.right = y;
} else { // 如果 x 是其父節(jié)點(diǎn)的左孩子,那么將 y 設(shè)為其父節(jié)點(diǎn)的左孩子
x.parent.left = y;
}
// 將 y 的右孩子設(shè)為 x,并將 x 的父親設(shè)為 y
y.right = x;
x.parent = y;
}
// 輔助方法,用 v 節(jié)點(diǎn)替換 u 節(jié)點(diǎn)
public void transplant(Node u, Node v) {
if (u.parent == null) { // 如果 u 是根節(jié)點(diǎn),那么將 v 設(shè)為新的根節(jié)點(diǎn)
this.root = v;
} else if (u == u.parent.left) { // 如果 u 是其父節(jié)點(diǎn)的左孩子,那么將 v 設(shè)為其父節(jié)點(diǎn)的左孩子
u.parent.left = v;
} else { // 如果 u 是其父節(jié)點(diǎn)的右孩子,那么將 v 設(shè)為其父節(jié)點(diǎn)的右孩子
u.parent.right = v;
}
if (v != null) { // 如果 v 不是 null,那么將 v 的父親設(shè)為 u 的父親
v.parent = u.parent;
}
}
// 輔助方法,返回以某個(gè)節(jié)點(diǎn)為根的子樹中最小鍵值的節(jié)點(diǎn)
public Node minimum(Node x) {
while (x.left != null) { // 沿著左孩子指針一直向下,直到找到最左邊的節(jié)點(diǎn)
x = x.left;
}
return x;
}
// 輔助方法,返回以某個(gè)節(jié)點(diǎn)為根的子樹中最大鍵值的節(jié)點(diǎn)
public Node maximum(Node x) {
while (x.right != null) { // 沿著右孩子指針一直向下,直到找到最右邊的節(jié)點(diǎn)
x = x.right;
}
return x;
}
// 輔助方法,返回樹中鍵值等于 key 的第一個(gè)找到的節(jié)點(diǎn)
public Node search(int key) {
Node x = this.root; // 從根節(jié)點(diǎn)開始查找
while (x != null && x.key != key) { // 如果 x 不是 null,并且 x 的鍵值不等于 key,那么繼續(xù)查找
if (key < x.key) { // 如果 key 小于 x 的鍵值,那么在 x 的左子樹中查找
x = x.left;
} else { // 如果 key 大于 x 的鍵值,那么在 x 的右子樹中查找
x = x.right;
}
}
return x; // 返回找到的節(jié)點(diǎn)或 null
}
// 向樹中插入一個(gè)鍵值為 key 的節(jié)點(diǎn)
public void insert(int key) {
Node z = new Node(key, 1); // 創(chuàng)建一個(gè)新節(jié)點(diǎn) z,并將其顏色設(shè)為紅色
Node y = null; // 初始化 y 為 null,用于記錄 z 的父節(jié)點(diǎn)
Node x = this.root; // 初始化 x 為根節(jié)點(diǎn),用于查找 z 的插入位置
while (x != null) { // 如果 x 不是 null,那么繼續(xù)查找
y = x; // 將 y 設(shè)為當(dāng)前的 x 節(jié)點(diǎn)
if (z.key < x.key) { // 如果 z 的鍵值小于 x 的鍵值,那么在 x 的左子樹中查找
x = x.left;
} else { // 如果 z 的鍵值大于或等于 x 的鍵值,那么在 x 的右子樹中查找
x = x.right;
}
}
z.parent = y; // 將 z 的父節(jié)點(diǎn)設(shè)為 y
if (y == null) { // 如果 y 是 null,說明樹是空的,那么將 z 設(shè)為根節(jié)點(diǎn)
this.root = z;
} else if (z.key < y.key) { // 如果 z 的鍵值小于 y 的鍵值,那么將 z 設(shè)為 y 的左孩子
y.left = z;
} else { // 如果 z 的鍵值大于或等于 y 的鍵值,那么將 z 設(shè)為 y 的右孩子
y.right = z;
}
insertFixup(z); // 調(diào)用插入修復(fù)方法,恢復(fù)紅黑性質(zhì)
}
// 插入修復(fù)方法,用于恢復(fù)紅黑樹的性質(zhì)
public void insertFixup(Node z) {
// 當(dāng) z 的父節(jié)點(diǎn)存在且是紅色時(shí),需要進(jìn)行調(diào)整
while (z.parent != null && z.parent.color == Color.RED) {
// 判斷 z 的父節(jié)點(diǎn)是其祖父節(jié)點(diǎn)的左孩子還是右孩子
if (z.parent == z.parent.parent.left) {
// 如果是左孩子,那么獲取 z 的叔叔節(jié)點(diǎn)(祖父節(jié)點(diǎn)的右孩子)
Node y = z.parent.parent.right;
// 根據(jù)叔叔節(jié)點(diǎn)的顏色分為三種情況
if (y != null && y.color == Color.RED) {
// 如果叔叔節(jié)點(diǎn)是紅色,那么將父節(jié)點(diǎn)和叔叔節(jié)點(diǎn)都變?yōu)楹谏?,將祖父?jié)點(diǎn)變?yōu)榧t色,并將祖父節(jié)點(diǎn)作為新的 z 節(jié)點(diǎn),繼續(xù)向上調(diào)整
z.parent.color = Color.BLACK;
y.color = Color.BLACK;
z.parent.parent.color = Color.RED;
z = z.parent.parent;
} else {
// 如果叔叔節(jié)點(diǎn)是黑色或 NIL,那么需要進(jìn)行旋轉(zhuǎn)操作
if (z == z.parent.right) {
// 如果 z 是其父節(jié)點(diǎn)的右孩子,那么先對(duì)父節(jié)點(diǎn)進(jìn)行左旋,然后交換 z 和其父節(jié)點(diǎn)的角色
z = z.parent;
leftRotate(z);
}
// 如果 z 是其父節(jié)點(diǎn)的左孩子,那么對(duì)祖父節(jié)點(diǎn)進(jìn)行右旋,然后將父節(jié)點(diǎn)變?yōu)楹谏?,將祖父?jié)點(diǎn)變?yōu)榧t色,并結(jié)束調(diào)整
z.parent.color = Color.BLACK;
z.parent.parent.color = Color.RED;
rightRotate(z.parent.parent);
}
} else {
// 如果是右孩子,那么獲取 z 的叔叔節(jié)點(diǎn)(祖父節(jié)點(diǎn)的左孩子),與上面類似,只是左右對(duì)稱
Node y = z.parent.parent.left;
// 根據(jù)叔叔節(jié)點(diǎn)的顏色分為三種情況
if (y != null && y.color == Color.RED) {
// 如果叔叔節(jié)點(diǎn)是紅色,那么將父節(jié)點(diǎn)和叔叔節(jié)點(diǎn)都變?yōu)楹谏?,將祖父?jié)點(diǎn)變?yōu)榧t色,并將祖父節(jié)點(diǎn)作為新的 z 節(jié)點(diǎn),繼續(xù)向上調(diào)整
z.parent.color = Color.BLACK;
y.color = Color.BLACK;
z.parent.parent.color = Color.RED;
z = z.parent.parent;
} else {
// 如果叔叔節(jié)點(diǎn)是黑色或 NIL,那么需要進(jìn)行旋轉(zhuǎn)操作
if (z == z.parent.left) {
// 如果 z 是其父節(jié)點(diǎn)的左孩子,那么先對(duì)父節(jié)點(diǎn)進(jìn)行右旋,然后交換 z 和其父節(jié)點(diǎn)的角色
z = z.parent;
rightRotate(z);
}
// 如果 z 是其父節(jié)點(diǎn)的右孩子,那么對(duì)祖父節(jié)點(diǎn)進(jìn)行左旋,然后將父節(jié)點(diǎn)變?yōu)楹谏瑢⒆娓腹?jié)點(diǎn)變?yōu)榧t色,并結(jié)束調(diào)整
z.parent.color = Color.BLACK;
z.parent.parent.color = Color.RED;
leftRotate(z.parent.parent);
}
}
}
// 最后確保根節(jié)點(diǎn)是黑色
this.root.color = Color.BLACK;
}
// 從樹中刪除一個(gè)鍵值為 key 的節(jié)點(diǎn)
public void delete(int key) {
Node z = search(key); // 查找要?jiǎng)h除的節(jié)點(diǎn)
if (z == null) { // 如果沒有找到,直接返回
return;
}
Node x; // 用于記錄替換后的新節(jié)點(diǎn)
Node y = z; // 用于記錄要?jiǎng)h除或移動(dòng)的原始節(jié)點(diǎn)
Color yOriginalColor = y.color; // 用于記錄 y 的原始顏色
if (z.left == null) { // 如果 z 沒有左孩子,那么用其右孩子替換它
x = z.right;
transplant(z, z.right);
} else if (z.right == null) { // 如果 z 沒有右孩子,那么用其左孩子替換它
x = z.left;
transplant(z, z.left);
} else { // 如果 z 有兩個(gè)孩子,那么用其后繼節(jié)點(diǎn)(右子樹中最小的節(jié)點(diǎn))替換它
y = minimum(z.right); // 找到 z 的后繼節(jié)點(diǎn)
yOriginalColor = y.color; // 記錄 y 的原始顏色
x = y.right; // 記錄 y 的右孩子
if (y.parent == z) { // 如果 y 是 z 的右孩子,那么直接將 x 的父親設(shè)為 y
x.parent = y;
} else { // 如果 y 不是 z 的右孩子,那么用 x 替換 y,并將 y 的右孩子設(shè)為 z 的右孩子
transplant(y, y.right);
y.right = z.right;
y.right.parent = y;
}
transplant(z, y); // 用 y 替換 z,并將 y 的左孩子設(shè)為 z 的左孩子
y.left = z.left;
y.left.parent = y;
y.color = z.color; // 將 y 的顏色設(shè)為 z 的顏色
}
if (yOriginalColor == Color.BLACK) { // 如果 y 的原始顏色是黑色,那么就存在過度黑問題,需要調(diào)用刪除修復(fù)方法,恢復(fù)紅黑性質(zhì)
deleteFixup(x);
}
}
// 刪除修復(fù)方法,用于恢復(fù)紅黑性質(zhì)
public void deleteFixup(Node x) {
// 當(dāng) x 不是根節(jié)點(diǎn),并且是黑色時(shí),需要進(jìn)行調(diào)整
while (x != this.root && x.color == Color.BLACK) {
// 判斷 x 是其父節(jié)點(diǎn)的左孩子還是右孩子
if (x == x.parent.left) {
// 如果是左孩子,那么獲取 x 的兄弟節(jié)點(diǎn)(父節(jié)點(diǎn)的右孩子)
Node w = x.parent.right;
// 根據(jù)兄弟節(jié)點(diǎn)的顏色分為四種情況
if (w.color == Color.RED) {
// 如果兄弟節(jié)點(diǎn)是紅色,那么將兄弟節(jié)點(diǎn)變?yōu)楹谏?,將父?jié)點(diǎn)變?yōu)榧t色,并對(duì)父節(jié)點(diǎn)進(jìn)行左旋,然后更新兄弟節(jié)點(diǎn)
w.color = Color.BLACK;
x.parent.color = Color.RED;
leftRotate(x.parent);
w = x.parent.right;
}
if (w.left.color == Color.BLACK && w.right.color == Color.BLACK) {
// 如果兄弟節(jié)點(diǎn)的兩個(gè)孩子都是黑色或 NIL,那么將兄弟節(jié)點(diǎn)變?yōu)榧t色,并將父節(jié)點(diǎn)作為新的 x 節(jié)點(diǎn),繼續(xù)向上調(diào)整
w.color = Color.RED;
x = x.parent;
} else {
// 如果兄弟節(jié)點(diǎn)的兩個(gè)孩子不都是黑色或 NIL,那么需要進(jìn)行旋轉(zhuǎn)操作
if (w.right.color == Color.BLACK) {
// 如果兄弟節(jié)點(diǎn)的右孩子是黑色或 NIL,那么將兄弟節(jié)點(diǎn)變?yōu)榧t色,將兄弟節(jié)點(diǎn)的左孩子變?yōu)楹谏?,并?duì)兄弟節(jié)點(diǎn)進(jìn)行右旋,然后更新兄弟節(jié)點(diǎn)
w.color = Color.RED;
w.left.color = Color.BLACK;
rightRotate(w);
w = x.parent.right;
}
// 如果兄弟節(jié)點(diǎn)的右孩子是紅色,那么將父節(jié)點(diǎn)的顏色賦給兄弟節(jié)點(diǎn),將父節(jié)點(diǎn)和兄弟節(jié)點(diǎn)的右孩子都變?yōu)楹谏?duì)父節(jié)點(diǎn)進(jìn)行左旋,然后結(jié)束調(diào)整
w.color = x.parent.color;
x.parent.color = Color.BLACK;
w.right.color = Color.BLACK;
leftRotate(x.parent);
x = this.root; // 將 x 設(shè)為根節(jié)點(diǎn),結(jié)束循環(huán)
}
} else {
// 如果是右孩子,那么獲取 x 的兄弟節(jié)點(diǎn)(父節(jié)點(diǎn)的左孩子),與上面類似,只是左右對(duì)稱
Node w = x.parent.left;
// 根據(jù)兄弟節(jié)點(diǎn)的顏色分為四種情況
if (w.color == Color.RED) {
// 如果兄弟節(jié)點(diǎn)是紅色,那么將兄弟節(jié)點(diǎn)變?yōu)楹谏?,將父?jié)點(diǎn)變?yōu)榧t色,并對(duì)父節(jié)點(diǎn)進(jìn)行右旋,然后更新兄弟節(jié)點(diǎn)
w.color = Color.BLACK;
x.parent.color = Color.RED;
rightRotate(x.parent);
w = x.parent.left;
}
if (w.right.color == Color.BLACK && w.left.color == Color.BLACK) {
// 如果兄弟節(jié)點(diǎn)的兩個(gè)孩子都是黑色或 NIL,那么將兄弟節(jié)點(diǎn)變?yōu)榧t色,并將父節(jié)點(diǎn)作為新的 x 節(jié)點(diǎn),繼續(xù)向上調(diào)整
w.color = Color.RED;
x = x.parent;
} else {
// 如果兄弟節(jié)點(diǎn)的兩個(gè)孩子不都是黑色或 NIL,那么需要進(jìn)行旋轉(zhuǎn)操作
if (w.left.color == Color.BLACK) {
// 如果兄弟節(jié)點(diǎn)的左孩子是黑色或 NIL,那么將兄弟節(jié)點(diǎn)變?yōu)榧t色,將兄弟節(jié)點(diǎn)的右孩子變?yōu)楹谏?,并?duì)兄弟節(jié)點(diǎn)進(jìn)行左旋,然后更新兄弟節(jié)點(diǎn)
w.color = Color.RED;
w.right.color = Color.BLACK;
leftRotate(w);
w = x.parent.left;
}
// 如果兄弟節(jié)點(diǎn)的左孩子是紅色,那么將父節(jié)點(diǎn)的顏色賦給兄弟節(jié)點(diǎn),將父節(jié)點(diǎn)和兄弟節(jié)點(diǎn)的左孩子都變?yōu)楹谏?,并?duì)父節(jié)點(diǎn)進(jìn)行右旋,然后結(jié)束調(diào)整
w.color = x.parent.color;
x.parent.color = Color.BLACK;
w.left.color = Color.BLACK;
rightRotate(x.parent);
x = this.root; // 將 x 設(shè)為根節(jié)點(diǎn),結(jié)束循環(huán)
}
}
}
// 最后確保根節(jié)點(diǎn)是黑色
this.root.color = Color.BLACK;
}