紅黑樹(插入和刪除原理)

二叉查找樹(BST)是一種父親節點的值大於左孩子的值,而小於右孩子的值,查找效率是O(logn),但是在插入方面,可能在某些情況下使整個樹退化成鏈表。所以爲了進一步實現優化,有了紅黑樹的數據結構。
紅黑樹的特點:
(1)在父親節點和子節點的關係上,和BST樹是一致的。
(2)每個節點都有顏色標示
(3)根節點是黑色
(4)葉子節點爲NULL,也必須是黑色
(5)如果一個節點是紅色,則兩個孩子節點是黑色
(6)對於每個節點,從該節點到所有後代葉子節點的路徑上,均包含相同數目的黑色節點

在這裏插入圖片描述
[圖片來源於網絡,侵權立刪]

因爲在刪除或插入的時候,會進行局部的旋轉或者變色(後面會說),來維持二叉查找樹的特性,而不會使整個樹退化成鏈表,紅黑樹根節點到任意子節點的最大距離不大於最小距離的二倍。

左旋轉:假設在某個X節點上作左旋轉,如圖(假設Z不爲NULL):

在這裏插入圖片描述
左旋轉後,X的右孩子成爲該子樹新的根節點,X變成新的新根節點的左孩子,而Y的左孩子轉換成了X的右孩子。
左旋轉流程圖
在這裏插入圖片描述

  • 僞代碼
左旋轉
LEFT_REPORT(T, x)
    y = right[x] //獲取x右孩子節點y
    right[x] = left[y] //將y的左孩子找到,作爲x的右孩子
    if(right[x] != null)  //要是右孩子不是null
        parent[left[y]] = x ; //將右孩子的父親節點設置成x
    parent[y] = parent[x] //將y的父親節點設置成原來x的父親節點
    if(parent[y] == null) //要是y父親節點是空的話
        root[T] = y //將y作爲當前根節點
    else if(x == left[parent[x]]) //要是X是左孩子
        left[parent[x]] = y //將y設置成x的父親節點的左孩子,也就是使用y代替x之前的位置
    else //否則就是右孩子
        right[parent[x]] = y  //使用y替換x的位置
    left[y] = x //講x設置成y的左孩子(因爲是左旋轉)
    parent[x] = y //將y設置成x的父親

右旋轉:即左旋轉的逆過程。
以上面第二個圖爲例子,以Y爲根節點的樹,左右旋轉,會將其左孩子轉換成根節點,而根節點Y會變成新的根節點的X的右孩子,最後X的右孩子轉換成Y的左孩子。

  • 右旋轉
右旋轉
RIGHT_REPORT(T, x)
    x = left[y] //獲取y的右孩子x
    left[y] = right[x]  //將x的右孩子作爲y的左孩子
    if(right[x]) //要是x的右孩子不爲null
        parent[right[x]] = left[y] //講右孩子的父親指針指向y
    parent[x] = parent[y] //讓x的父親指針指向根節點y的父親節點
    if(parent[y] == null) //要是y的父親節點是空,說明y就是根節點
        root[T] = x //講x設置成樹的根節點
    else if(y == left[parent[y]]) //x是其父親節點左孩子
        left[parent[x]] = x //將y原來的父親節點左指針指向x
    else 
        right[parent[x]] = x //使原來y的父親節點的右指針指向x
    right[x] = y //將x的右指針指向y
    parent[y] = x //將y的父親指針指向x

紅黑樹的插入操作,類似於於二叉搜索樹的插入操作,只是在他的基礎上進行了改進,先把節點按照二叉查找樹的插入方式進行插入,在把該插入的節點標記爲紅色(因爲從根節點到葉節點的黑節點數量要相等,所以暫時標記成紅色),插入以後需要維持二叉搜索樹的性質,所以就需要對部分子樹進行重新着色或者旋轉。使二叉樹重新平衡。

將新的節點加到樹中的過程就是不斷比較,比根節點大往右走,小於根節點,往左走,一直走到大於當前根節點小於根節點的右子樹。

完成左右和父親指針的指向,爲插入節點標記上紅色。

插入節點以後,樹是一顆BST樹,但是會失去紅黑樹的一些性質,需要變色和旋轉進行調整。

在這裏插入圖片描述
插入總結爲三種情況:

  • 插入節點的父親節點是紅色,如圖想在原先紅黑樹中插入Z節點,插入後,破壞了性質【紅色節點的孩子節點全爲黑色】
    所以需要進行變色操作,將插入的節點的父親節點和叔叔節點Y都塗成黑色。祖父節點7設置成紅色。變化後:

在這裏插入圖片描述
(要是插入的節點是父節點的右孩子,和上面的方法大同小異,相信聰明的你懂得!)
然後再繼續向上判斷是否破壞了紅黑樹的性質。

  • Z的叔叔節點是黑色,並且插入節點是根節點的右孩子
  • Z的叔叔節點是黑色,並且插入節點是根節點的左孩子

就情況2,如圖所示

在這裏插入圖片描述
這裏需要將以parent[z]作爲根節點左旋轉,轉換成情況3:
在這裏插入圖片描述
將z節點變成黑色,再將z節點的父親節點變成紅色,最後以z的父節點爲根進行右旋轉。這樣完成了紅黑樹的一次插入。

在這裏插入圖片描述
由上面的插入步驟來看,實現插入的三種情況是有轉換關係的,插入的時候,可以是第一種情況,然後再經過變色轉換成第二種情況後,那麼第二種情況通過旋轉又可以達到第三種情況,第三種情況是再經過旋轉就能又維護一個紅黑樹了。
也可以是第二種情況,轉換成第三種情況…這些過程通過z節點的動態變化來轉化。

下面是三種情況解決過程的僞代碼:

插入操作(在此操作之前有個插入操作,相當於BST插入節點的形式,比較簡單)
RB_INSERT_NODE
    while(color[parent[z]] == RED) //要是當前父親節點顏色是紅色
        if(parent[z] == left[parent[parent[z]]]) //當前節點的父親節點是其上一輩節點的左孩子
            y=right[parent[parent[z]]] //記錄下叔叔節點
        //情況1,z叔叔的顏色是紅色
        if(color[y]==RED) //要是叔叔節點也是紅色
            color[y] = BLACK
            color[parent[z]] = BLACK //將叔叔和父親節點都變成黑色
            color[parent[parent[z]]] = RED //祖父節點的顏色變成紅色
            z = parent[parent[z]] //將祖父節點的顏色通過z記錄下來,這裏重置了z的值!!!
        //第二種情況z是右孩子,叔叔節點是黑色
        else if(z == right[parent[z]])
            z=parent[z] //重新設置z爲當前根節點
            LEFT_ROTATE(T,z) //進行一次坐旋轉,進入第三種情況,注意左旋轉使Z的位置重新變化!!!
        //第三種情況,z是左孩子,叔叔節點爲黑色 
            color[parent[z]] = BLACK //將父親節點設置成黑色
            color[parent[parent[z]]]=RED //祖父節點設置成紅色
            //進行右旋轉
            RIGHT_ROTATE(T, parent[parent[z]])    //注意右旋轉使z的值重新變化!!!
        要是當前z節點的情況滿足while循環條件,繼續變色和旋轉
    color[root] = BLACK //重新設置根節點顏色
  • 刪除操作

如果刪除的節點是紅色的,則樹的性質不發生改變。要是黑色的話,則會導致紅黑樹的性質發生改變。
刪除節點主要可能破壞了以下性質:
如果刪除的節點y是黑色的
1.如果y是原來的根節點,而y的一個紅色孩子節點成爲了新的根節點,
2.如果x和parent[y](此時parent[x]=parent[y])都是紅色,則違反了【紅色節點的孩子節點都是黑色】這一性質(這裏的x代表刪除y以後,y位置的新節點)
3.刪除y將會導致先前包含y的路徑上黑節點個數減少1,使得【根節點到任何葉子節點的路徑上黑節點的個數相同】這一性質改變,改正這一問題的方法,可以將現在佔有y原來位置的節點x可以視爲包含一重額外的黑色,就是將包含(現在佔有y位置的節點)x所有路徑上的黑節點個數加1
4.當黑色節點被刪除時,將其黑色下推至其子節點,導致問題變成節點x即不是紅也不是黑,從而導致【每個節點是黑色,或者紅色】性質不成立。因爲給x增加一種顏色,即節點x是雙重黑色或者是紅黑色。這樣就分別給包含x的路徑上黑節點的個數貢獻2個或者1個,但是x的color仍然是RED(如果x是紅黑色)或者BLACK(如果x是雙重黑色)。一個節點額外的黑色反應在x指向他,而不是他的color屬性。

//刪除節點(前面操作和二叉查找樹的刪除差不多) 
RD_DELETE(T, z)
    if(left[z] == null || right[z] == null)
        y = z  //左孩子或者右孩子爲空
    else y = tree-sucessor(z) //否則的話,y就等於某個具體的孩子節點
    if(left[y] != null)
        x = left[y]  //使y的左指針指向x 
    else x = right[y]
    parent[x] = parent[y] //使x的父節點指針指向y 
    if(parent[y] == null) 
        root[T] = x //將x設置成新的根節點
    else if(y == left[parent[y]])
        left[parent[y]] = x 
    else right[parent[y]] = x 
    if(y!=z) 
        key[z] = key[y]
        y.left = z.left
        y.left.p = y 
        y.color = z.color
    if(color[y] == black)
        RB_DELETE_FIXUP(T,x) //用來恢復紅黑樹的性質
    return y

下面是恢復紅黑樹性質的過程了!
如果刪除的節點是紅色的話,紅黑樹的性質還是保持着,不用做修正操作。
要是刪除的是黑色節點,則導致包含該刪除節點的所有路徑少一個黑色節點,需要作修正操作。(要是刪除的是整棵樹唯一的黑色子節點,還是不用調整)
我們從替換被刪除節點的節點z開始,由於刪除節點導致少了一個黑色節點,我們可以認爲z有雙重顏色(多餘的那個顏色是一種假設),其中一個顏色是自帶的顏色,另一個顏色是從父親節點繼承下來。要是z節點的自身顏色是黑色,則刪除其父親節點後,他的顏色就變成了【黑黑】,否則就是【紅黑】,這樣是爲了保持性質【從根節點開始到任意每個葉子節點都經過相同數目的黑色節點】不變。然後我們回覆其他性質。

有以下情況:
1)當前節點z是紅+黑色
解法:直接把當前節點染成黑色,此時紅黑樹性質全部恢復
2)要是當前節點是黑+黑色且是根節點,什麼都不做,結束
3)當前節點是黑+黑色且兄弟節點爲紅色(此時父親節點和兄弟節點的子節點全爲黑)
如下圖:這種情況,w必須要有黑色子節點,所以改變w以及parent[z]的顏色,並對parent[z]做一次左旋轉。現在x的新兄弟節點是旋轉之前w的某個子節點,其顏色是黑色,轉換後性質沒發生變化,並將兄弟節點變成黑色。
在這裏插入圖片描述
實現僞代碼:

RD_DELETE_FIX(T,z)
    while(z != root && color[z] == black)
        if(z == left[parent[z]]) //這裏只考慮左子樹,右子樹的話同樣的道理
            w = right[parent[z]]
            if(color[w] == RED) 
                color[parent[z]] = RED
                LEFT_ROTATE(T, parent[z])   
                w = right[parent[x]]

4)當前節點是黑加黑,並且兄弟是黑色且兄弟節點兩個子節點全爲黑色。
解決:因爲w是黑色的,所以從z和w上去掉一重黑色,使得x只有一重黑色,而w爲紅色,爲了補償從z和w上去掉的一重黑色,在原來是紅色或者黑色的parent[z]上新增一重額外的黑色,通過將parent[z]作爲新節點z,來重複while循環。

在這裏插入圖片描述
即調用RB_INSERT_FIXUP(T, z)

if(color[left[w]] == black && color[right[w]] == black)
	color[w] = RED
	z = parent[z] 

5)z的兄弟節點w是黑色,w的左孩子節點是紅色,右孩子節點是黑色

在這裏插入圖片描述
可以交換w的左孩子和w的顏色,再進行右旋轉。現在z的新兄弟節點w是一個有着紅色有孩子的黑色節點

else if(color[right[w]] == BLACK) 
	color[left[w]] = BLACK
	color[w] = red
	right_rotate(T, w)
	w = right[parent[z]]

6)z的兄弟節點w是黑色,且w的右孩子節點是紅色的(也類似於經過上面一步轉化的結果)。
解決:將w節點的顏色設置成parent[z]節點的顏色,parent[z]節點的顏色設置成黑色,w的右孩子right[w]設置成黑色,以parent[z]作爲根節點進行左旋轉。完成刪除工作。
在這裏插入圖片描述

color[w] = color[left[w]]
color]parent[z]] = BLACK
color[right[w]] = BLACK
LEFT_ROTATE(T, parent[z])
x = root(T)

參考文章:

https://blog.csdn.net/chenhanzhun/article/details/38405041
https://github.com/julycoding/The-Art-Of-Programming-By-July/blob/master/ebook/zh/03.01.md
http://www.cnblogs.com/Anker/archive/2013/01/30/2882773.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章