數據結構 - 紅黑樹(Red Black Tree)刪除詳解與實現(Java)

  本篇要講的就是紅黑樹的刪除操做html

      紅黑樹插入操做請參考 數據結構 - 紅黑樹(Red Black Tree)插入詳解與實現(Java)node

  紅黑樹的刪除是紅黑樹操做中比較麻煩且比較有意思的一部分。數據結構

  在此以前,重申一遍紅黑樹的五個定義:ide

 

    1. 紅黑樹的節點不是黑色的就是紅色的學習

    2. 紅黑樹的根節點必定是黑色的this

    3. 紅黑樹的全部葉子節點都是黑色的(注意:紅黑樹的葉子節點指Nil節點)spa

    4. 紅黑樹任何路徑上不容許出現相鄰兩個紅色節點3d

    5. 從紅黑樹的任一節點開始向下到任意葉子節點所通過的黑色節點數目相同code

 

  接着,請你們謹記你操做的對象都是一顆標準的紅黑樹,因此不要腦補過多不能存在的狀況,若是你考慮的狀況不在本文的討論範圍以內,能夠往上看看是否是你的狀況違反了五條規則其中某一條,若還有疑問,歡迎留言討論。
htm

 

  

  D(Delete)表示待刪除節點

  P(Parent)表示待刪除節點的父節點

  S(Sibling)表示待刪除節點的兄弟節點

  U(Uncle)表示帶刪除節點的叔叔節點

  GP(Grandparent)表示待刪除節點的祖父節點

  XL(Left child of X)表示節點X的左子樹節點

  XR(Right child of X)表示節點X的右子樹節點

 

  刪除一個新的節點有如下四種狀況:

    1. 刪除的節點是葉子節點(非Nil)

    2. 刪除的節點只有左子樹

    3. 刪除的節點只有右子樹

    *4. 刪除的節點同時擁有左子樹和右子樹

  其實只有上面前三種狀況,對於第四種狀況,能夠找到待刪除節點的直接後繼節點,用這個節點的值代替待刪除節點,接着狀況轉變爲刪除這個直接後繼節點,狀況也變爲前三種之一。

  

  由於有不少狀況是不存在,待刪除節點是葉子節點(非Nil)的狀況稍微複雜一些,

  咱們下面先考慮待刪除的節點只有左子樹或只有右子樹的狀況。

  不存在的狀況包括

  ① 

  

  ②

  

  ☂ (san)

  

  ④

  

  ⑤

  

  ⑥

  

 

   請讀者分析一下上面不可能狀況的緣由,不復雜但必定要知道爲何。兩個節點的顏色紅黑狀況加上左右子樹狀況,總共八種狀況,上面已經排除了六種,剩下如下兩種可能的的狀況。

  ①

  DL表示DL節點本來的值

 

  ②

  DR表示DR節點本來的值

 

  這兩種狀況的調整操做比較簡單,直接用DL/DR的元素值代替D的元素,再把DL/DR直接刪去就好,操做事後不違反紅黑樹定義,刪除結束。

 

 

  刪除節點的四種狀況已經解決了三種,剩下最後一種了。

  待刪除的節點是葉子節點的狀況:

  由於待刪除的節點有多是紅色也多是黑色。

  若是待刪除節點是紅色的,那直接刪去這個節點,刪除結束。

  若是待刪除節點是黑色的,根據父節點P和兄弟節點S的狀況,可分爲如下五種狀況。

  狀況1:父節點P是紅色節點

      或者    

  這兩種狀況是同樣的,咱們討論第一個圖就好,當把D刪去後,從P的左子樹下來的黑色節點數目少了一,對應的調整作法爲,把P染成黑色,此時P左子樹的黑色結點數目恢復,但此時右子樹黑色結點數目多了一,再把S對應染成紅色便可

  圖例:

  

 

  狀況2:兄弟節點S是紅色節點

       或者     

 

  只能是這兩種情形,作法是把P染成紅色,S染成黑色,而後以P爲軸作相應的旋轉操做(若是D爲P的左子樹節點則以P爲軸作左旋操做,若是D爲P的右子樹節點則以P爲軸作右旋操做)

  圖例(以第一種情形爲例):

  

  到這裏就把狀況二變成了狀況一(父節點爲紅色)的狀況,接着按照狀況一的處理方式進行操做。

  

   狀況3:結點D的遠親侄子爲紅色節點的狀況

  

  此時父節點P的顏色可紅可黑,這種狀況的調整作法是,交換P和S的顏色,而後把遠侄子節點SR/SL設置爲黑色,再以P爲軸作相應的旋轉操做(若是D爲P的左子樹則左旋,若是D爲P的右子樹則右旋)

  圖例(以第一種情形爲例):

  

 

  調整先後從P點下來的全部路徑黑色節點數目沒有發生變化,刪除節點D後結束。(注意此處S的左子樹SL能夠爲Nil節點或者紅色節點,但依然是按照上面的規則進行調整,對結果沒有影響)

 

  狀況4:節點D的近親侄子爲紅色節點的狀況

  

  注意此處節點D的遠侄子節點必須爲Nil節點,不然就變成狀況3了。這種狀況的調整方式是,把S染成紅色,把近侄子節點SR/SL染成黑色,而後以節點S爲軸作相應的旋轉操做(若是D爲P的左子樹則以S爲軸作右旋操做,若是D爲P的右子樹則以S爲軸作左旋操做)。

  圖例(以第一種情形爲例)

  

  而後就真的變成狀況3了......接着按照狀況3的處理方式進行處理。

 

  狀況5:節點D,P,S均爲黑色節點

  

  以第一種情形爲例,這種狀況刪除D以後,從P的左子樹下來的黑色節點數目少了一,且沒有周圍也沒有紅節點來補全這個黑節點,作法就是把D刪去,而後把節點S染成紅色,這樣一來節點P的左右子樹路徑的黑色節點路徑就同樣了,但致使節點P整棵子樹的任意路徑的黑色節點數比其餘路徑少了一,此時咱們再從P開始(即把P當成D),但再也不刪除P,向上繼續調整,直到根節點(一直是狀況5)或者遇到狀況1~4並調整後結束。

   我看過幾篇文章,最後一種狀況基本講到我這裏就已經結束了,因此我在這種狀況上也所以多話了一點時間去理解。若此處有更詳細的例子,會更能幫助理解,因此我決定舉兩個例子,來講明什麼叫從P節點開始向上調整,哪一種狀況就是要直到根節點, 哪一種狀況就是遇到狀況1~4,而後調整後結束

  從節點P往上依然是全黑的狀況(父節點,兄弟節點均爲黑色)

  

  從節點P往上是其餘狀況

  

  這裏只是舉個例子,不管是變成狀況1~4的哪一種,通過調整以後都無需再繼續上溯,由於此時黑色節點數目已經恢復,且例子裏面GP不是根節點,由於根節點不可能爲紅色。

  

 

  下面倒序總結一下

  

  待刪除的節點是黑色葉子(非Nil)節點的狀況

  

 

 

   

  

  

   

  待刪除的節點是紅色葉子節點的狀況

  狀況6 直接刪除該節點

  待刪除的節點只擁有左子樹或只擁有右子樹的狀況

  

  待刪除的節點同時擁有左子樹和右子樹的狀況

  狀況9 找出直接後繼節點並轉變爲狀況1~8

   

 

  至此,關於紅黑樹刪除的全部狀況均討論完畢,以上的每一個字以及每一個圖都是本身寫本身畫的,花了很多時間,但願你們多看看,結合圖理解比較形象,完全搞懂紅黑樹的操做,代碼倒是次要的,由於同一種思路也有不一樣的代碼風格和實現方式。同時也但願這篇文章能對你們有幫助。

  

  下面是刪除的代碼:

  總的公共方法是這樣的,找到該元素對應的節點,而後刪除該節點:

    public boolean delete(int elem) {
        if (null == this.root) {
            return false;
        } else {
            TreeNode node = this.root;
            // find out the node need to be deleted
            while (null != node) {
                if (node.getElem() == elem) {
                    deleteNode(node);
                    return true;
                } else if (node.getElem() > elem) {
                    node = node.getLeft();
                } else {
                    node = node.getRight();
                }
            }
            return false;
        }
    }
delete(int elem)

  刪除節點的方法爲私有方法,包含了同時擁有左右子樹,只擁有左子樹以及只擁有右子樹的操做

    private void deleteNode(TreeNode node) {
        if(null  == node.getLeft() && null == node.getRight()) {
            if (node.getColor() == NodeColor.RED) {
                delete_red_leaf(node, true);
            } else {
                delete_black_leaf(node, true);
            }
        } else if (null == node.getLeft()) {
            // the node color must be black and the right child must be red node
            // replace the element of node with its right child's
            // cut off the the link between node and its right child
            node.setElem(node.getRight().getElem());
            node.setRight(null);
        } else if (null == node.getRight()) {
            node.setElem(node.getLeft().getElem());
            node.setLeft(null);
        } else {
            // both children are not null
            TreeNode next = node.getRight();
            while (null != next.getLeft()) {
                next = next.getLeft();
            }
            TreeUtils.swapTreeElem(node, next);
            deleteNode(next);
        }
    }
private void deleteNode(TreeNode node)

  由大及小,刪除的節點是紅色葉子節點的狀況,注意此處待刪除的節點確定不是根節點,因此不須要考慮該節點爲根節點的狀況

    private void delete_red_leaf(TreeNode node, boolean needDel) {
        TreeNode parent = node.getParent();
        if (node == parent.getLeft()) {
            parent.setLeft(null);
        } else {
            parent.setRight(null);
        }
    }
private void delete_red_leaf(TreeNode node, boolean needDel)

  最後就是最麻煩的刪除的刪除黑色葉子(非Nil)節點的狀況,找出兄弟節點,找出遠侄子節點,找出近侄子節點。

    private void delete_black_leaf(TreeNode node, boolean needDel) {
        TreeNode parent = node.getParent();
        if (null != parent) {
            boolean nodeInLeft = parent.getLeft() == node;
            TreeNode sibling = nodeInLeft ? parent.getRight() : parent.getLeft();
            TreeNode remoteNephew = null == sibling ? null : (nodeInLeft ? sibling.getRight() : sibling.getLeft());
            TreeNode nearNephew = null == sibling ? null : (nodeInLeft ? sibling.getLeft() : sibling.getRight());
            if (sibling.getColor() == NodeColor.RED) {
                delete_sibling_red(node);
            } else if (null != remoteNephew && remoteNephew.getColor() == NodeColor.RED) {
                delete_remote_nephew_red(node);
            } else if (null != nearNephew && remoteNephew.getColor() == NodeColor.RED) {
                delete_near_nephew_red(node);
            } else {
                // the sibling is also a leaf
                if (parent.getColor() == NodeColor.RED) {
                    delete_parent_red(node);
                } else {
                    sibling.setColor(NodeColor.RED);
                    delete_black_leaf(parent, false);
                }
            }
        }
        if (needDel) {
            if (null == parent) {
              this.root = null;
            } else if (node.getParent().getLeft() == node) {
                parent.setLeft(null);
            } else {
                parent.setRight(null);
            }
        }
    }
private void delete_black_leaf(TreeNode node, boolean needDel)

  刪除葉子節點包含了另一個參數 boolean needDel ,由於上面提到的有些狀況須要繼續上溯,因此有些節點不能被刪除。

 

 

  紅黑樹全部操做大功告成,但願對你們的學習有所幫助。

   PS:請問你們有能夠畫好看的二叉樹的軟件推薦嗎

 

 

 

  請尊重知識產權,引用轉載請通知做者!

相關文章
相關標籤/搜索