1. 程式人生 > >二叉排序樹、紅黑樹、AVL樹最簡單的理解

二叉排序樹、紅黑樹、AVL樹最簡單的理解

前言

[為什麼寫這篇]

之前在知乎上看過一個提問:為什麼紅黑樹比AVL樹用的場景更為廣泛?其實,這兩者應用場景都挺廣泛的。紅黑樹在 STL 和 Linux 都有一定的運用。而AVL樹也在 Windows程序地址空間管理 中得到了使用。既然紅黑樹和AVL樹這麼厲害,就要進一步瞭解一下它們到底是什麼。

基礎準備

[需要懂點資料結構哦]

紅黑樹和AVL都是來源於二叉排序樹,關於二叉搜尋樹的相關知識本文將會對一些簡單的概念和操作進行分析,更多的細節需要大家自己去進一步瞭解。(ps:演算法導論或許是一個不錯的選擇)

二叉排序樹

[一切為了查詢、插入、刪除方便]

我們都知道,線性表分為無序線性表和有序線性表。
無序線性表的資料並不是按升序或者降序來排列的,所以在插入和刪除時,沒有什麼必須遵守的規矩而可以插入在資料尾部或者刪除在資料尾部(將待刪除的資料和最後一個數據交換位置),但是在查詢的時候,需要遍歷整個資料集,影響了效率。
有序線性表的資料則想法,查詢的時候因為資料有序,可以用二分法、插值法、斐波那契查詢法來實現,但是,插入和刪除需要維護有序的結構,會耗費大量的時間。
為了提高插入和刪除的效率,二叉排序樹登場了。

二叉排序樹的定義

二叉排序樹 (BST) 是一棵具有下列性質的二叉樹。

  • 若它的左子樹不空,則左子樹上所有結點的值均小於它的根結構的值
  • 若它的右子樹不空,則右子樹上所有結點的值均大於它的根結構的值
  • 它的左子樹和右子樹都是二叉排序樹

定義中最為關鍵的特點是,左子樹結點一定比父結點小,右子樹結點一定比父結點大
二叉排序樹範例

二叉排序樹查詢

通過觀察上面的二叉排序樹,可以知道,查詢樹中一個值,可以從根結點開始查詢,和根結點的值做比較,比根結點的值小,就在根結點的左子樹中查詢,比根結點的值大,就在根結點的右子樹中查詢。其他結點的行為與根結點的行為也是一樣的。以此出發,可以得到遞迴演算法:

  • 如果樹是空的,則查詢結束,無匹配。
  • 如果被查詢的值和根結點的值相等,查詢成功。否則就在子樹中繼續查詢。如果被查詢的值小於根結點的值就選擇左子樹,大於根結點的值就選擇右子樹。
    在理想情況下,每次比較過後,樹會被砍掉一半,近乎折半查詢。

遍歷列印可以使用 中序遍歷 ,打印出來的結果是從小到大的有序陣列。

二叉排序樹插入

二叉排序的插入是建立在二叉排序的查詢之上的,原因很簡單,新增一個結點到合適的位置,就是通過查詢發現合適位置,把結點直接放進去。
先來說一下插入函式,SearchBST(BiTree T, int key,BiTree f,BiTree *p)中指標p具有非常重要的作用:

  • 若查詢的key已經有在樹中,則p指向該資料結點。
  • 若查詢的key沒有在樹中,則p指向查詢路徑上最後一個結點,而這裡的最後一個結點的位置和key應該被放入的位置存在著簡單關係(要麼當樹空時直接插入作為根結點, 要麼當樹非空時新結點作為查詢路徑終止結點的左孩子或者右孩子插入 )。

將上面的這些描述轉化為程式碼:

    InsertBST(BiTree *T,int key)
    {
        BiTree p,s;
        if(!SearchBST(*T,key,NULL&p))  /* 查詢不成功 */
        {
            s=(BiTree)malloc(sizeof(BiTree));
            s->data=key;
            s->lchild=s->rchild=NULL;
            if(!p)     /* 樹為空 */
                *T=s;   /* 在空樹中插入一個新結點作為根結點 */
            else if(key<p->data)
                p->lchild=s;
            else 
                p->rchild=s;
            return TRUE;
        }
        else
            return FALSE;   /*樹中已經有相應的key,不用插入*/
    }

藉助了二叉排序樹的查詢,輕鬆的找到新結點該放在哪個位置,然後把新結點對號入座放進去,就完成了二叉排序樹的插入操作。這中間並不會引起二叉樹其他部分的結構變化。

二叉排序樹刪除

二叉樹的刪除可不再像二叉樹的插入那麼容易了,以為刪除某個結點以後,會影響到樹的其它部分的結構,比如刪掉45,然後45的子孫們37、39、53將何處安放?
這裡寫圖片描述
刪除的時候需要考慮一下幾種情況:刪除的結點只有左子樹、刪除的結點只有右子樹、刪除的結點既有左子樹又有右子樹。
考慮前兩種情況,直接將左子樹或者右子樹替換被刪除的結點即可。
第三種情況,有左子樹和右子樹的情況。
這裡寫圖片描述
當把二叉排序樹進行中序遍歷,在序列中可以得到一個刪除結點s的直接前驅(或者直接後繼),用直接前驅p來替代s。
重點來看一下二叉排序樹的結點

/* 處理刪除結點後子樹拼接的三種情況 */
Status Delete(BiTree *p)
{
    BiTree q,s;
    if((*p)->rchild==NULL) /* 只有左子樹則只重接左子樹 */
    {
        q=*p;*p=(*p)->lchild;free(q);
    }else if((*p)->lchild==NULL)   /* 只有右子樹則只重接右子樹 */
    {
        q=*p;*p=(*p)->rchild;free(q); 
    }else  /* 左右子樹均不為空 */
    {
        q=*p;s=(*p)->lchild;
        while(s->rchild)   /* 找到左子樹的右盡頭(找到直接前驅)*/
        {
            q=s;s=s->rchild;
        }
        (*p)->data=s->data;  /* s指向被刪除結點的直接前驅 */
        if(q!=*p)
            q->rchild=s->lchild;  /* 重接q的右子樹 */
        else
            q->lchild=s->lchild;  /* 重接q的左子樹 */
        free(s); 
    }
    return TRUE;
}

這段大碼的內容分析了左右子樹均不為空(暫時不考慮刪除節點為根節點的)的情況,目的就是在與找到p的 左子樹的右盡頭 (因為右盡頭是待刪除結點的前驅結點),這個尋找的步驟就是while迴圈裡面指標s指向自身的右孩子:s=s->rchild.
找到右盡頭後,就要把右盡頭的左子樹(因為是右盡頭了,所以右盡頭只有左子樹沒有右子樹)拼接到q上,完成樹的移植工作。
ps:替換刪除節點有兩種方案:左子樹的右盡頭,或者是,右子樹的左盡頭。

二叉排序樹極端情況

二叉排序樹的優點在於保持了插入刪除不用移動元素只要修改指標的優點。在查詢上,查詢次數等於待查詢的結點在二叉排序樹的層級。
來看一種極端情況:
這裡寫圖片描述
這種有序陣列,查詢最後一個結點99需要經歷非常多的層級,其實查詢次數還是偏多的。這樣的情況下,樹是不平衡的,右側太重。
我們為了提高二叉排序樹的查詢效率,需要把樹構建得更為平衡,從而不出現左右偏重的情況。
這就引出了AVL樹和紅黑樹這兩種平衡二叉樹了。

AVL樹

AVL樹的定義

平衡二叉樹 (Height-Balanced Binary Search Tree) 是一種二叉排序樹,其中每一個結點的左子樹和右子樹的高度差不超過1(小於等於1)。
二叉樹的平衡因子 (Balance Factor) 等於該結點的左子樹深度減去右子樹深度的值稱為平衡因子。平衡因子只可能是-1,0,1。
距離插入結點最近的,且平衡因子的絕對值大於1的結點為根的自述,稱為最小不平衡子樹。

AVL樹的實現思路

平衡二叉樹就是二叉樹的構建過程中,每當插入一個結點,看是不是因為樹的插入破壞了樹的平衡性,若是,則找出最小不平衡樹。在保持二叉樹特性的前提下,調整最小不平衡子樹中各個結點之間的連結關係,進行相應的旋轉,使之成為新的平衡子樹。簡記為: 步步調整,步步平衡 。

AVL樹的實現過程

[左旋與右旋]
如上面提到的非平衡二叉樹,查詢的層級太多,如何減少這些曾經呢?這就要提到左旋和右旋了。先來看張圖
這裡寫圖片描述
左旋和右旋的過程我們可以看到平衡因子從(0,1,2)變為(0,0,0),即是一種將非平衡狀態轉換為平衡狀態的過程,這也是AVL樹步步調整的核心。
再來觀察一種複雜的情況
這裡寫圖片描述
新插入一個結點17,使得13的BF(-2)和21的BF(1)符號相反,如果直接左旋,調整後的樹就不再是二叉排序樹了。因此,正確做法是先在step1中調整符號,然後才能在step2中進行平衡操作。
由此,可以總結出平衡操作中非常必要的符號統一操作:

最小不平衡子樹的BF和它的子樹的BF符號相反時,就需要對結點先進行一次旋轉使得符號相同,再 反向旋轉一次 才能夠完成平衡操作。

[左旋程式碼實現]
這部分程式碼最好在紙上自己畫左旋圖更容易理解

/* 對以P為根的二叉排序樹左旋操作 */
void Left_Rotate(BiTree *P)
{
    BiTree R;
    R=(*P)->rchild; /* R指向P的右子樹根結點 */
    (*P)->rchilde=R->lchild; /* R的左子樹掛接為P的右子樹 */
    R->lchild=(*P);
    *P=R;    /* P指向新的根結點 */
}

[右旋程式碼實現]
這部分程式碼最好在紙上自己畫右旋圖更容易理解

/* 對以P為根的二叉排序樹右旋操作 */
void Right_Rotate(BiTree *P)
{
    BiTree L;
    L=(*P)->lchild; /* L指向P的左子樹根結點 */
    (*P)->lchilde=L->rchild; /* R的右子樹掛接為P的左子樹 */
    L->rchild=(*P);
    *P=L;    /* P指向新的根結點 */
}

AVL樹的左旋平衡、右旋平衡

AVL樹要在旋轉前要處理符號統一,這一步驟簡稱為 左平衡旋轉 和 右平衡旋轉 。
[左平衡旋轉處理程式碼]

#define LH +1   /* BF左高 */
#define EH  0   /* BF等高 */
#define RH -1  /* BF右高 */
/* 對以指標所指結點為根的二叉樹做左平衡旋轉處理 */
/* 演算法結束時,指標T指向新的根結點*/
void LeftBalance(BiTree *T)
{
    BiTree L,Lr;
    L=(*T)->lchild; /* L指向T的左子樹根結點 */
    switch(L->bf)
    {
        /* 檢查T的左子樹的平衡度,並做平衡處理 */
        case LH:/* 新結點插入在T的左孩子的左子樹上,要作單右旋處理 */
            (*T)->bf=L->bf=EH;
            Right_Rotate(T);
            break;
        case RH:/* 新結點插入在T的左孩子的右子樹上,要雙旋處理*/
            Lr=L->rchild; /* Lr指向T的左孩子的右子樹根 */
            switch(Lr->bf) /* 統一符號,修正T及其左孩子的平衡因子*/
            {
                case LH: 
                    (*T)->bf=RH;
                    L->bf=EH;
                    break;
                case EH:
                    (*T)->bf=L->bf=EH;
                    break;
                case RH:
                    (*T)->bf=EH;
                    L->bf=LH;
                    break;
            }
            Lr->bf=EH;
            Left_Rotate(&(*T)->lchild); /* 對T的左子樹作左旋平衡處理 */
            Right_Rotate(T); /* 對T作右旋平衡處理*/
    } 
}

右旋平衡的函式與左旋平衡的函式一樣,都是對插入新結點後,判斷是否需要做符號統一從而作雙旋操作。

AVL樹的實現演算法

[主函式]

/* 若在平衡的二叉排序樹T中不存在和e有相同關鍵字的結點,則插入一個 */
/* 資料元素為e的新結點並返回1,否則返回0。若因插入而使二叉樹失去平衡,則做平衡旋轉處理,taller反應T是否長高*/
Status InsertAVL(BiTree *T,int e,Status *taller)
{
    if(!*T)
    {
        /* 插入新結點,樹"長高",taller為TRUE */
        *T=(BiTree)malloc(sizeof(BiTNode));
        (*T)->data=e;
        (*T)->lchild=(*T)->rchild=NULL;
        (*T)->bf=EH;
        *taller=TRUE;
    }
    else
    {
        if(e==(*T)->data)
        { /* 樹中已存在和e有相同關鍵字的結點則不再輸入 */
            *taller=FALSE;
            return FALSE;
        }
        if(e<(*T)->data)
        { /* 在左子樹中進行搜尋 */
             if(!InsertAVL(&(*T)->lchild,e,taller))/* 未插入 */
             return FALSE;
             if(*taller) /* 已插入到T的左子樹中且左子樹長高*/
             {
                 switch((*T)->bf) /* 檢查T的平衡度 */
                 {
                     case LH: /* 左子樹高,左平衡處理 */
                         LeftBalance(T);
                         *taller=FALSE;
                         break;
                    case EH: /* 原本左右子樹等高,現因左子樹增高而樹增高 */
                        (*T)->BF=LH;
                        *taller=TRUE;
                        break;
                    case RH: /* 原本右子樹比左子樹高,現等高 */
                    (*T)->bf=EH;
                    *taller=FALSE;
                    break;
                 }
             }
        }
        else
        {/* 繼續在T的右子樹中進行搜尋 */
            if(!InsertAVL(&(*T)->rchild,e,taller))/* 未插入 */
            return FALSE;
            if(*taller) /* 已插入到Td 餓右子樹且右子樹"長高" */
            {
                switch((*T)->bf) /* 檢查T的平衡度 */
                {
                    case LH: /* 原本左子樹比右子樹高,現在左右子樹等高 */
                        (*T)->bf=EH;
                        *taller=FALSE;
                        break;
                    case EH: /* 原本左右子樹等高,現因右子樹增高而樹增高 */
                        (*T)->bf=RH;
                        *taller=TRUE;
                        break;
                    case RH: /*原本右子樹比左子樹高,需要做右平衡處理 */
                        RightBalance(T);
                        *taller=FALSE;
                        break;
                }
            }
        }
    }
    return TRUE;
}

程式碼內容比較多,核心在於對插入結點時,分配進入左右子樹,同時左旋平衡或右旋平衡並調整相應結點的bf。
至此,AVL樹的內容基本都囊括進去了,我們可以看到AVL樹每一步都要平衡,平衡因子不大於1。這種平衡是非常嚴格的平衡,還有其他形式的平衡,如多路查詢樹 (B樹、B+樹) 和  紅黑樹 。

紅黑樹

[不同方式的平衡]
平衡方式不只有AVL樹這種極端平衡的情況,還有其他的拓展平衡方式。

多路查詢樹

多路查詢樹包括B樹和拓展的B+樹,和二叉排序樹每個結點只能儲存一個元素,每個結點的孩子數不多於兩個的性質不一樣的是, 多路查詢樹每一個結點的孩子數可以多於兩個,每一個結點處都可以儲存多個元素 。
比如最簡單的2-3樹就是這樣一棵多路查詢樹:每一個結點都具有兩個孩子(稱為2結點)或三個孩子(稱為3結點)。需要注意的是:

  • 一個2結點要麼沒有孩子,要麼就要有兩個孩子,不能只有一個孩子。
  • 一個3結點包含一大一小兩個元素,要麼沒有孩子,要麼就要有三個孩子,不能只有一個或兩個孩子。

來看一下一個典型的2-3樹是什麼樣子的:
這裡寫圖片描述
2-3樹的插入刪除可以想象的到:涉及的操作有結點的分裂、合併、補位,這裡不做過多講解。

多路查詢樹的用途

2-3只是多路查詢樹的簡單特例,2-3樹是3階的B樹,在B樹上查詢的過程是一個 順時針查詢結點和在結點中查詢關鍵字的交叉過程 。
現在來說說B樹的用途,B樹的資料結構就是為內外村的資料互動準備的。
外存(如硬碟)是將 所有的資訊分割成相等大小的頁面,每次硬碟讀寫的都是一個或多個完整的頁面 。如果要處理的硬碟資料量很大,無法一次全部裝入記憶體中,就要對B樹進行調整,是的B樹的階數(或結點的元素)與硬碟儲存的頁面大小相匹配。比如一棵B樹的階為1001(1個結點可以包含1000個元素),高度為2,它可以儲存超過10億(1000X1000X1000)個關鍵字,我們只要讓根結點持久的保留在記憶體中,那麼在這棵樹上,尋找某一個關鍵字至多需要兩次硬碟的讀取。通過這種方式,在有限記憶體的情況下,每一次磁碟的訪問都可以獲得最大數量的資料。
而B+樹更是在B樹的基礎上,加上了在葉子結點的新的元素組織方式,將葉子結點連結在一起。即 出現在分支結點中的元素會被當作他們的該分支結點位置的中序後繼者(葉子結點)中再次列出。
另外,每一個葉子結點都會儲存一個指向後一葉子結點的指標 。
如圖,B+樹中的根結點元素4、6、9都被保留到葉子結點中,葉子結點也保留指向葉子結點的指標。
這裡寫圖片描述

紅黑樹的定義

紅黑樹是一棵二叉排序樹,它在每個結點上增加了一個儲存位來表示結點的顏色,可以是RED或BLACK。通過對任一條從根到葉子的簡單路徑上各個結點的顏色進行約束,紅黑樹確保沒有一條路徑會比其它路徑長2倍,因此是近乎平衡的。
樹中的結點包含5個屬性:color、key、left、right和p。如果一個結點沒有子結點或父結點,則該結點相應指標屬性值為NIL。
上面提到的這些都是為了讓大家有個直觀的感受,在具體的操作中,我們肯定是要在插入或者刪除結點的過程中時刻保持著紅黑樹的性質。
一棵紅黑樹是具有如下性質的二叉排序樹:

  • 每個結點的顏色只能是紅色或黑色的。
  • 根結點是黑色的。
  • 每個葉子結點(NIL)是黑色的。
  • 如果一個結點是紅色的,那麼它的兩個子結點都是黑色的。
  • 對每個結點,從該結點到其所有後代葉子簡單的簡單路徑上,均包含相同數目的黑色結點。

來看一個最典型的紅黑樹,以為葉子結點都是黑色的,所以統一出來當成NIL結點。紅色結點表示RED,灰色結點表示BLACK。
這裡寫圖片描述

紅黑樹的旋轉

為了維護上述紅黑樹的性質,必須調整結點的顏色和指標結構。
指標結構的修改是通過旋轉完成的。這是一種能保持二叉排序樹性質的區域性調整操作。這裡的左旋右旋操作和AVL樹中的左旋右旋一樣,具體程式碼可回到AVL樹部分去檢視。
這裡寫圖片描述

紅黑樹的插入

將新結點z插入到樹T中,然後將z塗成紅色,並呼叫 旋轉著色RB_INSERT_FIXUP函式 (在AVL樹中是旋轉平衡)來把插入新結點後的樹調整為紅黑樹。

RB_INSERT(T,z)

y=T.nil;
x=T.root
while x≠T.nil
    y=x;
    if z.key<x.key
    x=x.left
    else 
    x=x.right
z.p=y
if y==T.nil
    T.root=z
elseif z.key<y.key
    y.left=z
else y.right=z
z.left=T.nil
Z.right=T.nil
z.color=RED
RB_INSERT_FIXUP(T,z)

RB_INSERT_FIXUP(T,z)

wile z.p.color==RED
    if z.p==z.p.p.left
        y=z.p.p.right
        if y.color==RED    //case 1
            z.p.color =BLACK
            y.color=BLACK
            z.p.p.color=RED
            z=z.p.p
        else if z==z.p.right //case 2
            z=z.p
            LEFT_ROTATE(T,z)
        else //case 3
        z.p.color=BLACK
        z.p.p.color=RED
        RIGHT_ROTATE(T,z.p.p)
    else(same as then clause
            with "right" and "left" exchanged)
T.root.color=BLACK

這裡寫圖片描述
根據這個紅黑樹插入新結點的情況,我們來過一下程式碼流程。

(a)插入結點z。由於z和它的副結點z.p都是紅色的,違反了性質4(紅結點的後代要是黑結點)。由於z的叔結點y也是紅色的,適用於case1。結點被重新著色,並且指標z沿樹上升,如(b)所示。再一次z及其父結點都是紅色的,但z的叔結點y是黑色的。因為z是z.p的右孩子,可以應用case2。在執行一次左旋後,所得結果樹為(c)。現在,z是其父結點的左孩子,可以應用case3。重新著色並執行一次右旋後得(d)中的樹,它是一棵紅黑樹。

紅黑樹的刪除

從一棵紅黑樹中刪除結點的過程需要呼叫子過程RB_TRANSPLANT。
RB_TRANSPLANT(T,u,v)

if u.p==T.nil
    T.root=v
elseif u==u.p.left
    u.p.left=v
else u.p.right=v
v.p=u.p

紅黑樹的刪除和普通的二叉樹刪除一樣,只是需要對結點的顏色新增判斷,需要用更多的程式碼來記錄結點y的蹤跡,y有可能導致紅黑性質的破壞。當想要刪除結點z,且此事z的子結點少於2個時,z從樹中刪除,並讓y成為z。當z有兩個子結點時,y應該是z的後繼,並且y將移至樹中的z位置。在結點被移除或者在樹中移動之前,必須記住y的顏色,並且紀錄結點x的蹤跡,將x移至樹中y的原來位置,因為結點x也可能引起紅黑性質的破壞。刪除結點z之後,RB_DELETE呼叫一個輔助過程RB_DELETE_FIXUP,該過程通過改變顏色和旋轉來恢復紅黑性質。
RB_DELETE(T,z)

y=z
y-original-color=y.color
if z.left=T.nil
    x=z.right
    RB_TRANSPLANT(T,z,z.right)
elseif z.right==T.nil
    x=z.left
    RB_TRANSPLANT(T,z,z.left)
else y=TREE-MINMUM(z.right)
    y-orginal-color=y.color
    x=y.right
    if y.p==z
        x.p=y
    else RB_TRANSPLANT(T,z,y)
    y.left=z.left
    y.left.p=y
    y.color=z.color
if y-original-color==BLACK
    RB_DELETE_FIXUP(T,x)

上面的程式碼是跟隨結點y的移動追蹤過程。如果結點y是黑色的,紅黑樹性質遭到破壞,需要呼叫RB_DELETE_FIXUP進行補救。
RB_DELETE_FIXUP(T,x)

while x≠T.root and x.color==BLACK
    if x==x.p.left
        w=x.p.right
        if w.color==RED     //case 1
            w.color=BLACK
            x.p.color=RED
            LEFT_ROTATE(T,x.p)
            w=x.p.right
        if w.left.color==BLACK and w.right.color==BLACK  //case 2
            w.color=RED
            x=x.p
        else if w.right.color==BLACK    //case 3
            w.left.color=BLACK
            w.color=RED
            RIGHT_ROTATE(T,w)
            w=x.p.right
        w.color=x.p.color       //case 4
        x.p.color=BLACK
        w.right.color=BLACK
        LEFT_ROTATE(T,x.p)
        x=T.root
    else (same as then clause with "right" and "left" exchanged)
x.color=BLACK   

while迴圈的目標是將額外的黑色沿樹上移,直到:

  1. x指向紅黑結點,將x著色為黑色
  2. x指向根結點,可以簡單的“移除”額外的黑色
  3. 執行適當的旋轉和重新著色,退出迴圈

分析下面幾個狀態:紅色結點表示RED,灰色結點表示BLACK,棕紅色結點表示RED或BLACK,用c和c’表示。

這裡寫圖片描述
(a)通過結點B和D顏色交換和執行左旋,可將case 1轉化為case 2;
(b)在case 2中,將結點D著為紅色,並將x設為指向結點B後,由指標x所表示的額外黑色沿樹上升。如果通過case 1進入case 2,則while迴圈結束,因為新的結點x是紅黑的,因此其color屬性c是RED.
(c)通過結點C和D交換顏色並執行一次右旋,可以將case 3轉換成case 4。
(d)case 4中,通過改變某些結點的顏色並執行一次左旋,可以將由x表示的額外黑色去掉,終止迴圈。

這是最正宗的紅黑樹理解方式,還有一種方式是通過2-3樹分裂操作替換成顏色操作,具體請看博文查詢(一)史上最簡單清晰的紅黑樹講解

總結

作為平衡二叉排序樹眾多的實現之一,紅黑樹特別的 引入顏色 這一個約束條件保持樹的平衡。通過旋轉可以降低樹的高度並轉換顏色。

紅黑樹的插入和刪除操作是比較難理解的:它們都是建立在紅黑樹是平衡的情況下,加入一個結點或者刪除一個結點,影響了平衡,就需要向兄弟結點、父結點、叔結點進行借調和顏色互換,這一操作是通過旋轉實現的。但是換完以後,是否還是平衡的呢,這是不一定的,需要從被借調結點出發開始向上調整,直到整個樹都是平衡的。修復過程中,插入具體分3種情況,刪除分4中情況。

紅黑樹和AVL樹都是從樹的平衡性出發,找到合適的平衡方式,一個通過顏色標識限定,一個通過樹高差限定,使樹都處於平衡狀態,提高了演算法的實用性和效率。

參考資料

附錄

紅黑樹的java實現:

作者:美團點評技術團隊
連結:https://zhuanlan.zhihu.com/p/24367771
來源:知乎
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。

public class RBTreeNode<T extends Comparable<T>> {
    private T value;//node value
    private RBTreeNode<T> left;//left child pointer
    private RBTreeNode<T> right;//right child pointer
    private RBTreeNode<T> parent;//parent pointer
    private boolean red;//color is red or not red

    public RBTreeNode(){}
    public RBTreeNode(T value){this.value=value;}
    public RBTreeNode(T value,boolean isRed){this.value=value;this.red = isRed;}

    public T getValue() {
        return value;
    }
    void setValue(T value) {
        this.value = value;
    }
    RBTreeNode<T> getLeft() {
        return left;
    }
    void setLeft(RBTreeNode<T> left) {
        this.left = left;
    }
    RBTreeNode<T> getRight() {
        return right;
    }
    void setRight(RBTreeNode<T> right) {
        this.right = right;
    }
    RBTreeNode<T> getParent() {
        return parent;
    }
    void setParent(RBTreeNode<T> parent) {
        this.parent = parent;
    }
    boolean isRed() {
        return red;
    }
    boolean isBlack(){
        return !red;
    }
    /**
    * is leaf node
    **/
    boolean isLeaf(){
        return left==null && right==null;
    }

    void setRed(boolean red) {
        this.red = red;
    }

    void makeRed(){
        red=true;
    }
    void makeBlack(){
        red=false;
    }
    @Override
    public String toString(){
        return value.toString();
    }
}




public class RBTree<T extends Comparable<T>> {
    private final RBTreeNode<T> root;
    //node number
    private java.util.concurrent.atomic.AtomicLong size = 
                    new java.util.concurrent.atomic.AtomicLong(0);

    //in overwrite mode,all node's value can not  has same    value
    //in non-overwrite mode,node can have same value, suggest don't use non-overwrite mode.
    private volatile boolean overrideMode=true;

    public RBTree(){
        this.root = new RBTreeNode<T>();
    }

    public RBTree(boolean overrideMode){
        this();
        this.overrideMode=overrideMode;
    }


    public boolean isOverrideMode() {
        return overrideMode;
    }

    public void setOverrideMode(boolean overrideMode) {
        this.overrideMode = overrideMode;
    }

    /**
     * number of tree number
     * @return
     */
    public long getSize() {
        return size.get();
    }
    /**
     * get the root node
     * @return
     */
    private RBTreeNode<T> getRoot(){
        return root.getLeft();
    }

    /**
     * add value to a new node,if this value exist in this tree,
     * if value exist,it will return the exist value.otherwise return null
     * if override mode is true,if value exist in the tree,
     * it will override the old value in the tree
     * 
     * @param value
     * @return
     */
    public T addNode(T value){
        RBTreeNode<T> t = new RBTreeNode<T>(value);
        return addNode(t);
    }
    /**
     * find the value by give value(include key,key used for search,
     * other field is not used,@see compare method).if this value not exist return null
     * @param value
     * @return
     */
    public T find(T value){
        RBTreeNode<T> dataRoot = getRoot();
        while(dataRoot!=null){
            int cmp = dataRoot.getValue().compareTo(value);
            if(cmp<0){
                dataRoot = dataRoot.getRight();
            }else if(cmp>0){
                dataRoot = dataRoot.getLeft();
            }else{
                return dataRoot.getValue();
            }
        }
        return null;
    }
    /**
     * remove the node by give value,if this value not exists in tree return null
     * @param value include search key
     * @return the value contain in the removed node
     */
    public T remove(T value){
        RBTreeNode<T> dataRoot = getRoot();
        RBTreeNode<T> parent = root;

        while(dataRoot!=null){
            int cmp = dataRoot.getValue().compareTo(value);
            if(cmp<0){
                parent = dataRoot;
                dataRoot = dataRoot.getRight();
            }else if(cmp>0){
                parent = dataRoot;
                dataRoot = dataRoot.getLeft();
            }else{
                if(dataRoot.getRight()!=null){
                    RBTreeNode<T> min = removeMin(dataRoot.getRight());
                    //x used for fix color balance
                    RBTreeNode<T> x = min.getRight()==null ? min.getParent() : min.getRight();
                    boolean isParent = min.getRight()==null;

                    min.setLeft(dataRoot.getLeft());
                    setParent(dataRoot.getLeft(),min);
                    if(parent.getLeft()==dataRoot){
                        parent.setLeft(min);
                    }else{
                        parent.setRight(min);
                    }
                    setParent(min,parent);

                    boolean curMinIsBlack = min.isBlack();
                    //inherit dataRoot's color
                    min.setRed(dataRoot.isRed());

                    if(min!=dataRoot.getRight()){
                        min.setRight(dataRoot.getRight());
                        setParent(dataRoot.getRight(),min);
                    }
                    //remove a black node,need fix color
                    if(curMinIsBlack){
                        if(min!=dataRoot.getRight()){
                            fixRemove(x,isParent);
                        }else if(min.getRight()!=null){
                            fixRemove(min.getRight(),false);
                        }else{
                            fixRemove(min,true);
                        }
                    }
                }else{
                    setParent(dataRoot.getLeft(),parent);
                    if(parent.getLeft()==dataRoot){
                        parent.setLeft(dataRoot.getLeft());
                    }else{
                        parent.setRight(dataRoot.getLeft());
                    }
                    //current node is black and tree is not empty
                    if(dataRoot.isBlack() && !(root.getLeft()==null)){
                        RBTreeNode<T> x = dataRoot.getLeft()==null 
                                            ? parent :dataRoot.getLeft();
                        boolean isParent = dataRoot.getLeft()==null;
                        fixRemove(x,isParent);
                    }
                }
                setParent(dataRoot,null);
                dataRoot.setLeft(null);
                dataRoot.setRight(null);
                if(getRoot()!=null){
                    getRoot().setRed(false);
                    getRoot().setParent(null);
                }
                size.decrementAndGet();
                return dataRoot.getValue();
            }
        }
        return null;
    }
    /**
     * fix remove action
     * @param node
     * @param isParent
     */
    private void fixRemove(RBTreeNode<T> node,boolean isParent){
        RBTreeNode<T> cur = isParent ? null : node;
        boolean isRed = isParent ? false : node.isRed();
        RBTreeNode<T> parent = isParent ? node : node.getParent();

        while(!isRed && !isRoot(cur)){
            RBTreeNode<T> sibling = getSibling(cur,parent);
            //sibling is not null,due to before remove tree color is balance

            //if cur is a left node
            boolean isLeft = parent.getRight()==sibling;
            if(sibling.isRed() && !isLeft){//case 1
                //cur in right
                parent.makeRed();
                sibling.makeBlack();
                rotateRight(parent);
            }else if(sibling.isRed() && isLeft){
                //cur in left
                parent.makeRed();
                sibling.makeBlack();
                rotateLeft(parent);
            }else if(isBlack(sibling.getLeft()) && isBlack(sibling.getRight())){//case 2
                sibling.makeRed();
                cur = parent;
                isRed = cur.isRed();
                parent=parent.getParent();
            }else if(isLeft && !isBlack(sibling.getLeft()) 
                                    && isBlack(sibling.getRight())){//case 3
                sibling.makeRed();
                sibling.getLeft().makeBlack();
                rotateRight(sibling);
            }else if(!isLeft && !isBlack(sibling.getRight()) 
                                            && isBlack(sibling.getLeft()) ){
                sibling.makeRed();
                sibling.getRight().makeBlack();
                rotateLeft(sibling);
            }else if(isLeft && !isBlack(sibling.getRight())){//case 4
                sibling.setRed(parent.isRed());
                parent.makeBlack();
                sibling.getRight().makeBlack();
                rotateLeft(parent);
                cur=getRoot();
            }else if(!isLeft && !isBlack(sibling.getLeft())){
                sibling.setRed(parent.isRed());
                parent.makeBlack();
                sibling.getLeft().makeBlack();
                rotateRight(parent);
                cur=getRoot();
            }
        }
        if(isRed){
            cur.makeBlack();
        }
        if(getRoot()!=null){
            getRoot().setRed(false);
            getRoot().setParent(null);
        }

    }
    //get sibling node
    private RBTreeNode<T> getSibling(RBTreeNode<T> node,RBTreeNode<T> parent){
        parent = node==null ? parent : node.getParent();
        if(node==null){
            return parent.getLeft()==null ? parent.getRight() : parent.getLeft();
        }
        if(node==parent.getLeft()){
            return parent.getRight();
        }else{
            return parent.getLeft();
        }
    }

    private boolean isBlack(RBTreeNode<T> node){
        return node==null || node.isBlack();
    }
    private boolean isRoot(RBTreeNode<T> node){
        return root.getLeft() == node && node.getParent()==null;
    }
    /**
     * find the successor node
     * @param node current node's right node
     * @return<