淺嘗紅黑樹

目錄


紅黑樹的優點

  • 一棵有n個內部結點的紅黑樹的高度最可能是2lg(n+1)
  • 進行基本動態集合操做如查找、插入、刪除等的時間複雜度最壞爲O(log n)

紅黑樹的應用

  • C++的STL中的set和map
  • Linux的虛擬內存管理
  • 關聯數組的實現
  • 等等

五條紅黑性質

  1. 每一個結點不是紅就是黑(非紅即黑)
  2. 根結點是黑的(根黑)
  3. 每一個葉結點:指樹尾端NIL指針或NULL結點,是黑的(NIL/NULL黑)
  4. 若是一個結點是紅的,那麼它的倆個兒子都是黑的,即從每一個葉子到根的全部路徑上都不能有兩個連續的紅色結點(紅父黑孩)
  5. 對於任一結點而言,其到葉結點樹尾端NIL指針的每一條路徑都包含相同數目的黑結點(路徑黑同數)

樹的旋轉知識

左旋

  • 當前結點的右孩子y成爲該孩子樹新的根,y的左孩子變爲當前結點的右孩子
左旋僞代碼:
Left-Rotate(T, x){
    y = x.right  //定義y是x的右孩子
    x.right = y.left  //y的左孩子成爲x的右孩子
    if(y.left != T.nil)  y.left.p = x //若是y左孩子不是nil
    y.p = x.p  //x結點成爲y的父結點
    if(x.p == T.nil)  T.root = y  //若是x是根結點,y成爲根結點
    else if(x == x.p.left)  x.p.left = y  //肯定y新成爲左孩子仍是右孩子
    else x.p.right = y
    y.left = x  //x成爲y的左孩子
    x.p = y
}
對左旋的理解
  • 經過以上代碼,能夠看到被旋轉的結點會變成一個左結點,若是被旋轉結點的右孩子擁有左孩子,那就被繼承到被旋轉結點的右孩子那了

右旋

  • 當前結點的左孩子y成爲該孩子樹新的根,y的右孩子變爲當前結點的左孩子
右旋僞代碼
Right-Rotate(T,x){
    y = x.left //定義y是x的左孩子
    x.left = y.right //y的右孩子成爲x的左孩子
    if(y.right != T.nil) y.left.p = x
    y.p = x.p //x結點成爲y的父結點
    if(x.p == T.nil) T.root = y //若是x是根結點,y成爲根結點
    else if(x == x.p.left) x.p.left = y //肯定y新成爲左孩子仍是右孩子
    else x.p.right = y
    y.right = x //x成爲y的左孩子
    x.p = y
}
對右旋的理解
  • 經過以上代碼,能夠看到被旋轉的結點會變成一個右結點,若是被旋轉結點的左孩子擁有右孩子,那就被繼承到被旋轉結點的左孩子那了

紅黑樹的插入

插入僞代碼

RB-Insert(T,z){
    y = nil 
    x = T.root
    while(x != T.nil){
        y = x //找出z的父結點y的位置
        if(z.key < x.key) x = x.left
        else x = x.right
    }
    z.p = y
    if(y == nil[T]) T.root = z//判斷z要插入的位置
    else if(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)
}
爲何插入的結點要染成紅色呢?
  • 我想的是,無論是染成紅仍是黑,性質1,2,3都是知足的
  • 試想咱們從頭構建一棵紅黑樹,當只有一個黑色的根結點的時候,若是染成黑色就不知足性質5了,推廣到新插入結點的父結點都是黑色的話,染成黑色一定是不知足性質5的了
  • 而染成紅色能夠在一些狀況下少違背一條性質

插入修復僞代碼

RB-Insert-Fixup(T,z){
    while(z.p.color == RED){//全部修復狀況父結點都是紅色
        if(z.p == z.p.p.left){
            y = z.p.p.right
            if(y.color == RED){//狀況1:叔叔結點是紅色
                z.p.color = BLACK//父變黑
                y.color = BLACK//叔變黑
                z.p.p.color = RED//祖變紅
                z = z.p.p//將祖父當作新增結點z,z上移兩個指針且爲紅色
            }
            else {//叔叔結點是黑色
                if(z == z.p.right){//狀況2:當前結點是父結點的右子
                    z = z.p//父與子角色互換
                    Left-Rotate(T,z)//左旋
                }//狀況3:當前結點是父結點的左子
                z.p.color = BLACK//父變黑
                z.p.p.color = RED//祖變紅
                Right-Rotate(T,z.p.p)//祖右旋
            }
        }
        else{
            y = z.p.p.left
            if(y && y.color == RED){
                z.p.color = BLACK
                y.color = BLACK
                z.p.p.color = RED
                z = z.p.p
            }
            else{
                if(z == z.p.left){
                    z = z.p
                    Right-Rotate(T,z)
                }
                z.p.color = BLACK
                z.p.p.color = RED
                Left-Rotate(T,z.p.p)
            }
        }
    }
    T.root.color = BLACK//根確定是黑的
    return root
}

(特此說明:當前代指當前結點,父代指父結點,叔代指父的兄弟結點,祖代指祖父結點,數字1,2,3,4,5代指上述對應性質)數組

不用修復的狀況:

插入的是根結點
  • 直接染黑當前
插入的結點的父結點是黑色
  • 紅黑樹沒有被破壞,因此什麼也不用作。

插入修復狀況1

  • 分析:父和叔都是紅
  • 對策:父和叔染黑,祖染紅,祖爲新當前,重新當前從新分析執行
爲何這麼作?
  • 當前與父都是紅,違背4,染黑父
  • 染黑父以後,包含父的路徑違背5,染紅祖
  • 染紅祖以後,包含叔的路徑違背5 ,染黑叔
  • 祖不必定知足4,若祖爲根,染黑祖
  • 若祖不爲根,設置祖爲當前,從新分析執行

插入修復狀況2

  • 分析:父爲紅,叔爲黑,當前是右子
  • 對策:父做新當前,新當前左旋
爲何這麼作?
  • 父爲新當前,由於左旋能夠使兒子上移
  • 左旋後,若兒子爲根,染黑;不爲根,從新分析父
  • 爲什麼設置父爲新當前?處理須要從下到上,從葉到根處理
  • 經過這麼作以後,某些狀況下便到達了狀況3的地步

插入修復狀況3

  • 分析:父爲紅,叔爲黑,當前是左子
  • 對策:父染黑,祖染紅,祖右旋
爲何這麼作?
  • 當前和父爲紅,違背4,染黑父
  • 染黑父,違背5,染紅祖,以祖旋轉

紅黑樹的刪除

刪除僞代碼

RB-Transplant(T,x,y){
    //找到安放y的位置
    //用y頂替x的位置
    if(x.p == T.nil) T.root = y
    else if(x == x.p.left) x.p.left = y
    else x.p.right = y
    y.p = x.p
}

RB-Delete(T,z){
    y = z //記錄要刪除結點的原信息
    y-origial-color = y.color
    if(z.left == T.nil){//被刪除結點只有一個孩子的狀況
        x = z.right
        RB-Transplant(T,z,z.right)
    }
    else if(z.right == T.nil){//被刪除結點只有一個孩子的狀況
        x = z.left
        RB-Transplant(T,z,z.left)
    }
    else{//y是z的後繼結點
        y = Tree-Minium(z.right) //這裏也能夠是Tree-Maxmun(z.left),不過下面要修改
        y-origial-color = y.color
        x = y.right
        if(y.p == z) x.p = y//若是y是z的直系孩子,z右邊只有一個孩子的狀況,綁定x和y
        else{
            RB-Transplant(T,y,y.right)//用y.right頂替y的位置
            y.right = z.right//y接管z的右孩子
            y.right.p = y
        }
        RB-Transplant(T,z,y)//用y頂替z的位置
        y.left = z.left//y接管z的左孩子
        y.left.p = y
        y.color = z.color//y接管z的顏色
    }//若是y原來是黑色的,進行修復
    if(y-origial-color == BLACK) RB-Delete-Fixup(T,x)
}

刪除修復僞代碼

RB-Delete-Fixup(T,z){
    while(z != T.root && z.color == BLACK){//z一直往上移
        if(z == z.p.left){
            w = z.p.right//w是z的兄弟結點
            if(w.color == RED){//狀況1:w是紅色
                w.color = BLACK //兄變黑
                z.p.color = RED //父變紅
                Left-Rotate(T,z.p) //父左旋
                w = z.p.right //重置兄
            }
            if(w.left.color == BLACK && w.right.color == BLACK){//狀況2:兄和其兩孩子都是黑
                w.color = RED //兄變紅
                z = z.p //父爲新當前
            }
            else{
                if(w.right.color == BLACK){ //狀況3:兄的孩左紅右黑
                    w.left.color = BLACK //兄左孩變黑
                    w.color = RED //兄變紅
                    Right-Rotate(T,w) //兄右旋
                    w = z.p.right //重置兄
                } //狀況4:兄的孩左右皆紅
                w.color = z.p.color //兄顏色變爲父
                z.p.color = BLACK //父變黑
                w.right.color = BLACK //兄右孩變黑
                Left-Rotate(T,z.p) //父左旋
                z = T.root //當前爲根
            }
        }
        else{
            w = z.p.left//w是z的兄弟結點
            if(w.color == RED){//狀況1:w是紅色
            w.color = BLACK //兄變黑
            z.p.color = RED //父變紅
            Right-Rotate(T,z.p) //父右旋
            w = z.p.left //重置兄
            }
            if(w.right.color == BLACK && w.left.color == BLACK){//狀況2:兄和其兩孩子都是黑
                w.color = RED //兄變紅
                z = z.p //父爲新當前
            }
            else{
                if(w.left.color == BLACK){ //狀況3:兄的孩右紅左黑
                    w.right.color = BLACK //兄右孩變黑
                    w.color = RED //兄變紅
                    Left-Rotate(T,w) //兄左旋
                    w = z.p.left //重置兄
                } //狀況4:兄的孩左右皆紅
                w.color = z.p.color //兄顏色變爲父
                z.p.color = BLACK //父變黑
                w.left.color = BLACK //兄左孩變黑
                Right-Rotate(T,z.p) //父右旋
                z = T.root //當前爲根
            }
        }
    }
    z.color = BLACK
}

理解修復的準備

  • 假設被刪除結點的後繼結點包含一個額外的黑色,也保留原來的顏色
  • 緣由:將紅黑樹按二叉查找樹的方法刪除結點後,可能會違反2,4,5,將其包含額外的黑色便可保持5
  • 由於引入額外顏色,違反1

修復思路

  • 將包含額外黑色的結點不斷上移
  • 當結點爲紅+黑,直接染黑
  • 當結點指向根,直接染黑
  • 其它狀況以下所列,轉化爲以上狀況

刪除修復狀況1

  • 分析:當前爲黑+黑,兄爲紅,父爲黑,兄的孩子都是黑
  • 對策:兄染黑,父染紅,父左旋,重置兄
問一句爲何?
  • 爲了將其轉化爲其它三種狀況
  • 父左旋提高了兄,爲保持已有狀態,需父變紅,兄變黑,由於兄被提高,需從新設定兄弟關係

刪除修復狀況2

  • 分析:當前爲黑+黑,兄和其兩孩子都是黑
  • 解法:兄染紅,父爲新當前
問一句爲何?
  • 根據思路,將額外的黑色上移,在這既轉移給父
  • 致使通過兄的路徑的黑色數都增長一,違反5
  • 將兄染成紅便可
  • 此時若父爲紅+黑,直接染黑
  • 若父爲黑+黑,則繼續處理

刪除修復狀況3

  • 分析:當前爲黑+黑,兄爲黑,兄的孩子左紅右黑
  • 解法:兄的左孩子染黑,兄染紅,兄右旋,後重置當前的兄弟節點
問一句爲何?
  • 將其轉換爲狀況4
  • 爲此需右旋兄,直接右旋違反4
  • 先將兄的左孩子染黑,兄染紅
  • 兄發生了變化,需重置當前的兄弟節點

刪除修復狀況4

  • 分析:當前爲黑+黑,兄爲黑,兄的右子爲紅,兄的左子隨意
  • 解法:將兄染成父,父染黑,兄的右子染黑,父左旋,設當前爲根節點ssh

    問一句爲何?
  • 目的:去除當前身上的額外黑色
  • 若直接將父左旋,違背4,爲此染黑父
  • 但染黑父後左旋,將致使:
  • 通過當前和根的路徑違背5,因此可直接去除當前身上額外的黑色
  • 通過根和兄左子的路徑違背5,因此可互換父和兄的顏色,由於父已染黑,亦可將兄染成父
  • 通過根和兄右子的路徑違背5,因此可在知足上一條件時,將兄右子染黑url

後記

  • 因理解尚淺,尚有表達錯誤或修改之處,勞煩指出
  • 須要圖畫的可查看書本或引用,在此不表,請諒解
  • 後續可能會嘗試實現,望與君共勉
  • 若需轉載,麻煩註明博客園-AnnsShadoW

引用參考

《算法導論》中文第三版
百度百科-紅黑樹
教你透徹瞭解紅黑樹.net

相關文章
相關標籤/搜索