終於到紅黑樹了,紅黑樹在軟件中使用是比較多的,也是一個重點難點,接下來就慢慢啃掉這個紅黑樹。
7.1 紅黑樹的性質
7.1.1 引入紅黑樹
前面有講了平衡二叉樹,平衡二叉樹是一個強平衡,要保證每個結點的平衡因子都不大於1,就因爲是強平衡,導致性能不是很好,所以纔有紅黑樹的出現,紅黑樹是一個弱平衡。
紅黑樹是在每個結點上添加一個存儲位來表示結點的顏色,可以是RED或BLACK,通過對任何一條從根結點到葉子的簡單路徑上各個結點的顏色進行約束,紅黑樹確保沒有一條路徑會比其他路徑長出2倍,因而進似平衡。
7.1.2 紅黑樹的性質
一棵紅黑樹是滿足下面紅黑性質的二叉搜索樹:
- 每個結點或是紅色的,或是黑色的
- 根結點是黑色的
- 每個葉結點(NIL)是黑色的
- 如果一個結點是紅色的,則它的兩個子結點都是黑色的
- 對每個結點,從該結點到其所有後代葉結點的簡單路徑上,均包含相同數目的黑色結點
7.1.3 nil結點
爲了便於處理紅黑樹代碼中的邊界條件,使用了一個nil結點來代替NIL,所有指向NIL的指針都指向了nil結點。
7.1.4 黑高
從某個結點x出發(不包含該結點)到達一個葉結點的任意一條簡單路徑上的黑色結點個數稱爲該結點的黑高(black-high),記爲bh(x)。
7.1.5 紅黑樹圖
這個是紅黑樹的圖,滿足上面的性質,圖片來源是Kind老師課件上的。
7.1.6 紅黑樹的結構
typedef int Elemtype;
typedef enum
{
RED = 0,
BLACLK,
}_color;
#define RBTREE_ENTRY(name, type) \
struct name \
{ \
struct type *left; \
struct type *right; \
struct type *parent; \
_color color; \
}
typedef struct rbTree_node
{
Elemtype data; //結點數據
RBTREE_ENTRY(, rbTree_node) rbst; //紅黑樹結點信息
}_rbTree_node;
typedef struct rbTree
{
struct rbTree_node *root; //指向根結點
struct rbTree_node *nil; //nil結點
}_rbTree;
紅黑樹的結點多了雙親結點和顏色結點
7.2 紅黑樹的旋轉
7.2.1 旋轉的說明
平衡二叉樹的時候旋轉講的很詳細了, 因爲那時候剛接觸,看了好幾遍才能看懂,現在一看就懂了,所以只是簡單的講解就可以了。
先上圖:
左右旋就一個圖可以搞定的,當初在寫平衡二叉樹的時候, 不清楚,現在清楚了,就用一個圖。
左旋:
x結點變成了y的左孩子,y的左孩子變成了x的右孩子,x的父結點的孩子指向y。
右旋:
y結點變成了x的右孩子,x的右孩子變成了y的左孩子,y的父結點的孩子指向x。
7.2.2 旋轉的代碼
左旋:
/**
* @brief 左旋
* @param
* @retval
*/
static int rbTree_left_Rotate(struct rbTree *T, struct rbTree_node *x)
{
//獲取x的右孩子y
struct rbTree_node *y = x->rbst.right;
//1.調整x父結點的孩子指向y,y的parent指針執行x的父
y->rbst.parent = x->rbst.parent;
if(x->rbst.parent == T->nil) { //注意這個nil結點
T->root = y;
} else if(x == x->rbst.parent.left) {
x->rbst.parent.left = y;
} else if(x == x->rbst.parent.right) {
x->rbst.parent.right = y;
}
//2.y左孩子掛在x右孩子上
x->rbst.right = y->rbst.left;
if(y->rbst.left != T->nil) {
y->rbst.left->bst.parent = x;
}
//3.x成爲y的左孩子
y->rbst.left = x;
x->rbst.parent = y;
return 0;
}
右旋:
/**
* @brief 右旋
* @param
* @retval
*/
static int rbTree_right_Rotate(struct rbTree *T, struct rbTree_node *y)
{
//獲取y的左孩子x
struct rbTree_node *x = y->rbst.left;
//1.調整y父結點的孩子指向x,x的parent指針執行x的父
x->rbst.parent = y->rbst.parent;
if(y->rbst.parent == T->nil) { //注意這個nil結點
T->root = x;
} else if(y == y->rbst.parent.left) {
y->rbst.parent.left = x;
} else if(y == y->rbst.parent.right) {
y->rbst.parent.right = x;
}
//2.x右孩子掛在y左孩子上
y->rbst.left = x->rbst.right;
if(x->rbst.right != T->nil) {
x->rbst.right->bst.parent = y;
}
//3.y成爲x的右孩子
x->rbst.right = y;
y->rbst.parent = x;
return 0;
}
旋轉並不難,只要把3個指向轉換的關係理清楚就好。
7.3 紅黑樹的插入
到紅黑樹的插入了,說實話,插入操作比刪除操作簡單很多,因爲插入操作是一個結點一個結點插入的,再插入之前這個紅黑樹就已經平衡了,在插入的時候,調整的範圍也比較少。
7.3.1 創建結點
因爲我們知道紅黑樹的結點是有兩種顏色的,一種是RED,一種是BLACK,但是我們在創建結點的時候,這個結點的顏色是選擇RED還是BLACK,下面先看一個圖:
還是用這個圖,我們在4結點的時候插入右子樹,是不是感覺插入紅色結點就不需要調整了,在673結點插入左子樹的時候,是不是也是插入紅色結點就不要調整了,但是如果插入的是黑色結點,不管在哪個位置插入都需要調整,因爲插入黑色結點就會違背性質5,本來之前的紅黑樹的黑高是一樣的,你插入了黑色結點之後黑高就肯定不一樣了,所以插入結點我們選擇插入紅色。
創建結點的代碼:
/**
* @brief 創建結點
* @param
* @retval
*/
static struct rbTree_node* rbTree_creat_node(struct rbTree *T, Elemtype data, _color color)
{
//申請一個結點
struct rbTree_node *node = (struct rbTree_node *)malloc(sizeof(struct rbTree_node));
assert(node);
//填充數據
node->data = data;
node->bst.color = color;
node->bst.left = T->nil;
node->bst.right = T->nil;
node->bst.parent = T->nil;
return node;
}
比較簡單,就是單純的創建結點,然後賦值。
7.3.2 插入結點
插入結點這個也比較簡單,主要按照,插入的結點的data值遍歷一下整個紅黑樹,然後找到要插入的位置,再調整一個這個位置的指針,把這個新的結點的插入到紅黑樹即可。插入的結點是否滿足紅黑樹的性質,需要怎麼調整,我們下節再分析:
/**
* @brief 紅黑樹插入操作
* @param
* @retval
*/
static int rbTree_Insert(struct rbTree *T, struct rbTree_node *z)
{
struct rbTree_node *y = T->nil; //y是x的父節點
struct rbTree_node *x = T->root; //x是查找當前要插入的結點
//循環遍歷找到要插入的位置
while(x != T->nil)
{
y = x;
if(z->data < x->data) { //數據小於data,往左子樹
x = x->rbst.left;
} else if(z->data > x->data) {
x = x->rbst.right;
}else { //兩個數相等退出
return -1;
}
}
//把z掛接到y的孩子上
z->rbst.parent = y; //把z的父結點指針指向y
if(z->rbst.parent == T->nil) { //如果z的父節點爲nil,說明現在是空樹
T->root = z;
} else if(z->data < y->data ) { //數據比根結點數據小,掛在左子樹上
y->rbst.left = z;
} else {
y->rbst.right = z;
}
//調整,下節講
return 0;
}
7.3.3 調整結點
這個就比較難了,因爲要涉及到調整,這裏我們一步步插入,這樣會更熟悉:
原始數據:{24,25,13,35,23, 26,67,47,38,98, 20,19,17,49,12, 21,9,18,14,15}
-
結點24
我們插入的是紅色結點,這個需要調整成黑色。 -
結點25,結點13
這兩個結點爲紅色,因爲父結點爲黑色,所以補需要調整 -
結點35
插入35結點是紅色的,這就違背了紅黑樹的性質4,所以需要調整,這時候的叔父結點是紅色的,
調整操作:父結點和叔父結點同時變成黑色,因爲之前是紅色,所以如果有子結點都是黑色,不會違背性質4和性質5,祖父結點變成紅色,z的指針往上移動兩層,繼續遍歷,根結點變成黑色。
調整後:
又滿足了紅黑樹的性質。 -
結點23
-
結點26
這個結點26就比較複雜了,因爲結點26的叔父結點是黑色的,25的左孩子沒有的話就是指向nil,nil就是黑色的,並且這個結點26是左孩子,所以需要右旋:
現在這樣就變成另外一個情況了,叔父結點爲黑色,自己爲右孩子,這樣的處理方式是把父結點變成黑色,祖父結點變成紅色,然後以祖父結點爲軸心左旋,然後就可以符合紅黑樹的性質了
-
結點38
中間的結點自己模擬插入就可,上面已經講過了
38這裏有了子樹,看起來比較多,不多操作也是一樣的,
叔父結點是黑色,並且自己是右孩子,開始變顏色,並左旋
-
最後
其他結點我就不畫了,插入到之後會形成這樣的一個紅黑樹
看圖確實還是挺平衡的,這裏有一個連接,是可以看到紅黑樹的插入調整步驟的網頁,學習的時候可以藉助這個工具
更多參考代碼: http://github.com/wangbojing
好像我的本地的,這裏貼它github的連接
代碼:
/**
* @brief 紅黑樹插入調整
* @param
* @retval
*/
static int rbTree_insert_fixup(struct rbTree *T, struct rbTree_node *z)
{
//父結點是nil結點,比較父結點是紅色的,需要調整
while(z->rbst.parent->rbst.color == RED)
{
//叔父結點是在右子樹上
if(z == z->rbst.parent->rbst.parent->rbst.left) {
//判斷叔父結點的顏色
struct rbTree_node *y = z->rbst.parent->rbst.parent->rbst.right;
if(y->rbst.color == RED) {
//父結點和叔父結點同時變成黑色,祖父結點變成紅色,z的指針往上移動兩層,繼續遍歷
z->rbst.parent->rbst.color = BLACK;
y->rbst.color = BLACK;
z->rbst.parent->rbst.parent->rbst.color = RED;
z = z->rbst.parent->rbst.parent;
} else {
if(z == z->rbst.parent->rbst.right) { //叔父結點是黑色並且z是右孩子
z = z->rbst.parent;
rbTree_left_Rotate(T, z);
}
z->rbst.parent->rbst.color = BLACK; //叔父結點爲黑色並且z是左孩子
z->rbst.parent->rbst.parent->rbst.color = RED;
rbTree_right_Rotate(T, z->rbst.parent->rbst.parent);
}
}else {
//判斷叔父結點的顏色
struct rbTree_node *y = z->rbst.parent->rbst.parent->rbst.left;
if(y->rbst.color == RED) {
//父結點和叔父結點同時變成黑色,祖父結點變成紅色,z的指針往上移動兩層,繼續遍歷
z->rbst.parent->rbst.color = BLACK;
y->rbst.color = BLACK;
z->rbst.parent->rbst.parent->rbst.color = RED;
z = z->rbst.parent->rbst.parent;
} else {
if(z == z->rbst.parent->rbst.left) { //叔父結點是黑色並且z是左孩子
z = z->rbst.parent;
rbTree_right_Rotate(T, z); //左旋後,變成是右孩子了
}
z->rbst.parent->rbst.color = BLACK; //叔父結點爲黑色並且z是右孩子
z->rbst.parent->rbst.parent->rbst.color = RED;
rbTree_left_Rotate(T, z->rbst.parent->rbst.parent);
}
}
}
T->root->rbst.color = BLACK;
return 0;
}
7.4 紅黑樹的其他函數
在說刪除之前,先補充幾個其他函數,因爲這幾個函數在刪除的時候,是要用到的,誰叫刪除那麼難。
7.4.1 最小結點函數
紅黑樹也是排序二叉樹,所以在這個樹裏面,最小的值就是在最左邊,所以只需要不斷的查找最左邊的值,即可找到最小值。
代碼:
/**
* @brief 查找x爲根結點的子樹的最小結點
* @param
* @retval
*/
static struct rbTree_node *rbTree_Min(struct rbTree *T, struct rbTree_node *x)
{
while(x->bst.left != T->nil)
{
x = x->bst.left;
}
return x;
}
7.4.2 最大結點函數
有最小結點就有最大結點,操作剛好是相反的。
代碼:
/**
* @brief 查找x爲根結點的子樹的最大結點
* @param
* @retval
*/
static struct rbTree_node *rbTree_Max(struct rbTree *T, struct rbTree_node *x)
{
while(x->bst.right != T->nil)
{
x = x->bst.right;
}
return x;
}
7.4.3 搜索結點函數
傳入data數據,匹配找到需要刪除的結點,這是刪除的時候需要的
/**
* @brief 查找x爲根結點的子樹的最大結點
* @param
* @retval
*/
static struct rbTree_node *rbTree_search(struct rbTree *T, Elemtype data)
{
struct rbTree_node *node = T->root;
while(node != T->nil)
{
if(data < node->data) {
node = node->rbst.left;
} else if(data > node->data) {
node = node->rbst.right;
}else {
return node;
}
}
return T->nil;
}
7.4.4 查找x結點的直接後繼
在二叉樹刪除的時候,如果刪除這個結點有兩個子樹的情況下,就需要尋找直接後繼或者是直接前繼,這裏就封裝成一個函數了,刪除的時候可以直接使用。
/**
* @brief 查找x爲根結點的子樹的最大結點
* @param
* @retval
*/
static struct rbTree_node *rbTree_successor(struct rbTree *T, rbTree_node *x)
{
struct rbTree_node *y = x->rbst.parent;
//如果有右子樹的話,就直接找右子樹的最小值,就是直接後繼
if(x->rbst.right != T->nil) {
return rbTree_Min(T, x->rbst.right);
}
//如果沒有右子樹,就往父結點走,當遇到一個爲左子樹的時候,這個時候的父結點就是直接後繼
while((y != T->nil) && (x == y->rbst.right)) {
//後面的判斷條件如果是left的話,就說明找到了直接後繼
x = y;
y = y->rbst.parent;
}
return y;
}
這樣一寫,感覺當初寫的排序二叉樹,感覺有點不靠譜,以後有空再回來重寫一遍。
7.5 紅黑樹的刪除
插入操作還算簡單了,就是刪除操作比較難,不過再難也要逼着學會,因爲紅黑樹確實很有用。
7.5.1 刪除結點
紅黑樹的刪除和二叉樹的刪除一樣,要刪除的結點也是分爲3種情況:
-
沒有左右子樹
沒有左右子樹的情況下,就直接刪除。 -
有左子樹或者右子樹
直接用子樹替代要刪除的結點。 -
有左子樹且有右子樹
這個就有點難受,刪除最難受的就是這種情況,要找到被刪除元素的直接後繼,然後用這個直接後繼替換要刪除的元素,原來的直接後繼的子樹替換成直接後繼
代碼:
/**
* @brief 紅黑樹插入操作
* @param x結點替換y結點
* @retval
*/
static int rbTree_replace(struct rbTree *T, struct rbTree_node *y, struct rbTree_node *x)
{
if(y->rbst.parent == T->nil) { //y是根結點
T->root = x;
} else if(y == y->rbst.parent->rbst.left) { //x是y的左孩子
y->rbst.parent->rbst.left = x;
} else if(y == y->rbst.parent->rbst.right) { //x是y的右孩子
y->rbst.parent->rbst.right = x;
}
x->rbst.parent = y->rbst.parent;
return 0;
}
/**
* @brief 紅黑樹刪除操作
* @param
* @retval
*/
int rbTree_Delete(struct rbTree *T, struct rbTree_node *z)
{
struct rbTree_node *y = z; //y是指向要被刪除的結點或者是移動的結點
struct rbTree_node *x = T->nil;
_color y_old_color = y->rbst.color; //保存y以前的顏色
if(z->rbst.left == T->nil) { //刪除的z沒有左孩子
x = z->rbst.right; //保存x結點,x結點也是有移動過的
rbTree_replace(T, z, z->rbst.right); //用x替換到y
} else if(z->rbst.right == T->nil) {
x = z->rbst.left; //保存x結點,x結點也是有移動過的
rbTree_replace(T, z, z->rbst.left); //用x替換到y
} else {
y = rbTree_successor(T, z); //查找直接後繼
y_old_color = y->rbst.color;
x = y->rbst.right;
if(z == y->rbst.parent) { //如果y就是z的孩子
x->rbst.parent = y;
} else {
rbTree_replace(T, y, y->rbst.right); //先把y結點給替換掉
y->rbst.right = z->rbst.right; //把z的右子樹掛在到y的右子樹上
y->rbst.right->rbst.parent = y;
}
rbTree_replace(T, z, y); //用x替換到y
//y結點替換z結點
y->rbst.left = z->rbst.left;
y->rbst.left->rbst.parent = y;
y->rbst.color = z->rbst.color;
}
//調整
free(z);
if(y_old_color == BLACK)
rbTree_delete_fixup(T, x);
return 0;
}
代碼中的分情況已經很明確了,每一種情況都是一個if,只要細心觀看就沒有問題。
7.5.2 需要調整的情況
如果y是紅色的
- 前面兩種情況下,y爲父結點的時候,孩子結點一定有兩個,要不然黑高不平衡,所以不考慮
- 最後一種有兩個子樹的時候,y是z的孩子結點,y替換z,x替換y,如果y是紅色的,那z是黑色的,x也是黑色的,到時候y會替換z的位置,顏色也變成黑色。黑高沒有改變,也沒有相連的兩個紅色結點。
- y不是z的孩子結點的時候,原來的y的右孩子x,會代替y,y是紅結點,所以x是黑結點,不存在。
- y是紅色結點,所以不會是根結點。
如果y是黑色的,會有以下幾點破壞性質
- 原來y就是根結點,y的一個紅色的孩子成爲新的根結點
- 如果x和x.p都是紅色的,違反了性質4
- y在樹中移動的之後,相等於把y給刪除了,這時候y的所有祖先都的黑高都小1,不平衡。
7.5.3 刪除調整
按照插入的方式,把刪除過一遍,希望過了這一邊,就知道刪除的詳細信息了。
刪除操作,是先刪除掉結點,然後再進行調整
- 刪除24結點
原始數據:
刪除24結點:
刪除24結點,然後24結點的後繼結點25,替換掉24結點,如圖
替換完成之後,跟w是左孩子時的情況4是一樣的
w爲左孩子時情況4:x的兄弟結點是w黑色結點,且w的左孩子是紅色的
操作如下:
w->rbst.color = x->rbst.parent->rbst.color; //x的父節點的顏色賦值給w結點的顏色
x->rbst.parent->rbst.color = BLACK; //x的父節點的顏色賦值成黑色
w->rbst.left->rbst.color = BLACK; //w的左孩子的顏色賦值成黑色
rbTree_right_Rotate(T, x->rbst.parent); //然後以x的父節點爲軸心右旋
結果如圖:
2. 刪除25結點
刪除25結點,雖然25結點是葉子結點,沒有左右子樹,但是因爲要刪除的這個結點y是黑色的,這樣一刪除的話,樹的黑高的不一樣了,所以需要調整。
刪除後,調整之前,會把25的右孩子替換掉25,這裏的25的右孩子就是nil結點,因爲nil是黑色的,w是21結點也是黑色的,21的左右孩子都是nil,也就是都是黑色的,這個符合w爲左孩子時的情況2
w爲左孩子時情況2:x的兄弟結點是w黑色結點,而且w的兩個子結點都是黑色的
操作如下:
w->rbst.color = RED; //因爲x少了一個黑色結點,所以要把w的這邊也少一個黑色
x = x->rbst.parent; //再把x的指針往上移動,但是現在這個x還是確實一個黑色,還要繼續調整
這是做了一次操作之後,這是x=23結點,然後這次符合了w爲左孩子時的情況4,操作細節看上面,這裏直接看結果:
醜是醜了點了,但是結果還是可以達到紅黑樹的性質的
-
刪除13
這個簡單就不畫了 -
刪除結點35
因爲35結點只有一個孩子,所以35結點的孩子替換35結點,
但是因爲35結點是y也是黑色的,所以調整,但是因爲x是紅色的,所以只要把x設置爲黑色即可滿足性質,這個就解決了違背性質4的問題。
-
刪除結點23
這個跟上面的一樣就不寫了 -
刪除結點26
終於對根結點動手了, 這次看看刪除根結點有什麼情況:
要把26結點刪除,就要找到26結點的直接後繼,這裏的26結點的直接後繼是38結點,使用38替換結點26
這時候的x是47結點的左孩子,所以w是67結點,這個還是符合w爲右孩子是的情況4,具體就不寫,結果如下:
-
刪除結點67
省略 -
刪除結點47
這次來了一個有看頭的了,刪除結點47,47的直接後繼結點49,直接替換47,結果:
這時候符合情況2,具體操作不畫了,
這是x是指向49結點的,w是指向17結點,符合情況1
w爲左孩子是情況1:x的兄弟結點w是紅色的
操作如下:
w->rbst.color = BLACK; //把w設成黑色的
x->rbst.parent->rbst.color = RED; //把x的父節點設置成紅色
rbTree_right_Rotate(T, x->rbst.parent); //然後進行右旋
w = x->rbst.parent->rbst->left; //調整w
調整完的結果:
這時候又符合情況2,所以繼續調整
這時候就是x和x.p都爲紅色了,把x.p改爲黑色,即可完成平衡。
-
刪除好多結點
中間刪除的結點可以省略,操作都差不多 -
刪除結點21
原來的樹:
這種情況符合情況1,操作完成之後
這種情況終於符合w爲左孩子時,左孩子爲黑色結點
w爲左孩子時情況3:x的兄弟結點w是黑色的,w的左孩子是黑色的,w的右孩子是紅色的
操作如下:
w->rbst.right->rbst.color = BLACK;
w->rbst.color = RED;
rbTree_left_Rotate(T, w);
w = x->rbst.parent->rbst.left;
操作完之後:
這種情況又回到了情況4,按情況4操作即可得到:
紅黑樹再次平衡。
還剩下5個結點我這裏就不刪除了,前面已經把這麼多情況已經描述清楚了,後面也是這樣了。
7.5.4 調整代碼
/**
* @brief 紅黑樹插入調整
* @param
* @retval
*/
static int rbTree_delete_fixup(struct rbTree *T, struct rbTree_node *x)
{
while(x != T->root && x->rbst.color == BLACK)
{
if(x == x->rbst.left) { //x是左孩子
struct rbTree_node *w = x->rbst.parent->rbst.right;
if(w->rbst.color == RED) { //情況1:x的兄弟結點w是紅色的
w->rbst.color = BLACK;
x->rbst.parent->rbst.color = RED;
rbTree_left_Rotate(T, x->rbst.parent);
w = x->rbst.parent->rbst->right;
}
if(w->rbst.left->rbst.color == BLACK && w->rbst.right->rbst.color == BLACK) {
//情況2:x的兄弟結點w是黑色的,而且w的兩個子結點都是黑色的。
w->rbst.color = RED;
//因爲x少了一個黑色結點,所以要把w的這邊也少一個黑色
x = x->rbst.parent;
//再把x的指針往上移動,但是現在這個x還是確實一個黑色,還要繼續調整
} else {
if(w->rbst.right->rbst.color == BLACK) {
//情況3:x的兄弟結點是w黑色結點,w的右孩子是黑色的
w->rbst.left->rbst.color = BLACK;
w->rbst.color = RED;
rbTree_right_Rotate(T, w);
w = x->rbst.parent->rbst.right;
}
//情況4:x的兄弟結點是w黑色結點,且w的右孩子是紅色的
w->rbst.color = x->rbst.parent->rbst.color;
//x的父節點的顏色賦值給w結點的顏色
w->rbst.parent->rbst.color = BLACK; //x的父節點的顏色賦值成黑色
w->rbst.right->rbst.color = BLACK; //w的右孩子的顏色賦值成黑色
rbTree_left_Rotate(T, x->rbst.parent); //然後以x的父節點爲軸心右旋
x = T->root;
}
} else if(x == x->rbst.right) { //x是右孩子
struct rbTree_node *w = x->rbst.parent->rbst.left;
if(w->rbst.color == RED) { //情況1:x的兄弟結點w是紅色的
w->rbst.color = BLACK;
x->rbst.parent->rbst.color = RED;
rbTree_right_Rotate(T, x->rbst.parent);
w = x->rbst.parent->rbst->left;
}
if(w->rbst.left->rbst.color == BLACK && w->rbst.right->rbst.color == BLACK) {
//情況2:x的兄弟結點w是黑色的,而且w的兩個子結點都是黑色的。
w->rbst.color = RED;
//因爲x少了一個黑色結點,所以要把w的這邊也少一個黑色
x = x->rbst.parent;
//再把x的指針往上移動,但是現在這個x還是確實一個黑色,還要繼續調整
} else {
if(w->rbst.left->rbst.color == BLACK) {
//情況3:x的兄弟結點是w黑色結點,w的左孩子是黑色的
w->rbst.right->rbst.color = BLACK;
w->rbst.color = RED;
rbTree_left_Rotate(T, w);
w = x->rbst.parent->rbst.left;
}
//情況4:x的兄弟結點是w黑色結點,且w的左孩子是紅色的
w->rbst.color = x->rbst.parent->rbst.color;
//x的父節點的顏色賦值給w結點的顏色
x->rbst.parent->rbst.color = BLACK; //x的父節點的顏色賦值成黑色
w->rbst.left->rbst.color = BLACK; //w的左孩子的顏色賦值成黑色
rbTree_right_Rotate(T, x->rbst.parent); //然後以x的父節點爲軸心右旋
x = T->root;
}
}
}
x->rbst.color = BLACK;
return 0;
}
有一個可以在線插入刪除紅黑樹的連接,學習的時候可以使用這個連接,看看紅黑樹的變化。
https://www.cs.usfca.edu/~galles/visualization/RedBlack.html