紅黑樹--(高清無碼圖+代碼)演示

      介紹紅黑樹以前,咱們先來簡單瞭解下二叉查找樹(BST)。java

      首先咱們看下二叉查找樹的特性:spa

          1.若任意節點的左子樹不空,則左子樹上全部結點的值均小於或等於它的根結點的值。3d

          2.若任意節點的右子樹不空,則右子樹上全部結點的值均大於或等於它的根結點的值。code

          3.左、右子樹也分別爲二叉排序樹。cdn


      如上圖,這是一顆二叉樹,若是咱們要查詢7,那麼只要先跟8比,而後再跟3比,再跟6比,而後找到7,咱們能夠發現,這正是二分查找的思想,查詢二叉樹中的一個數據,最大的查詢次數就是這顆樹的高度。同理,當插入數據的時候,也是從根節點開始依次比較找到位置,再插入。可是二叉樹也存在着缺陷,那就是當出現跛腳樹的時候,二叉樹就會變成了線性查找。舉個例子。咱們有一個根節點8,若是依次插入7,6,5,4,那麼這棵二叉樹就會變成下面這個模樣:blog


      我去,這仍是我認識的樹嘛。。。咱們發現,樹的高度就是元素的個數,查詢效率變成了O(n)。爲了解決二叉樹的平衡問題,下面咱們就來引出主角--紅黑樹(Red–black tree)。排序

      紅黑樹(英語:Red–black tree)是一種自平衡二叉查找樹,是每一個節點都帶有顏色屬性的二叉查找樹,顏色爲紅色或黑色。在二叉查找樹強制通常要求之外,對於任何有效的紅黑樹咱們增長了以下的額外要求:
遞歸

  1. 節點是紅色或黑色。
  2. 根是黑色。
  3. 全部葉子都是黑色(葉子是NIL節點)。
  4. 每一個紅色節點必須有兩個黑色的子節點。(從每一個葉子到根的全部路徑上不能有兩個連續的紅色節點。)
  5. 從任一節點到其每一個葉子的全部簡單路徑都包含相同數目的黑色節點。

下圖是一顆紅黑樹it


      這5個特性,確保了二叉樹的大體平衡:從根到葉子的最長的可能路徑很少於最短的可能路徑的兩倍長。(最長路徑爲黑和紅相間隔,最短路徑爲全黑節點)io

      當有插入和刪除操做的時候,紅黑樹的平衡最容易被打破,接下來咱們詳細分析下各類場景下的破壞,紅黑樹是如何保持平衡的。

插入

      這裏先明確一個點,咱們把新插入的節點標記爲紅色(爲何呢?若是設爲黑色,就會致使根到葉子的路徑上有一條路上,多一個額外的黑節點,這個是很難調整的。可是設爲紅色節點後,可能會致使出現兩個連續紅色節點的衝突,那麼能夠經過1⃣️變色和2⃣️樹旋轉來調整。)

爲了便於理解,如今開頭和你們普及幾個概念。(祖父節點,叔節點)

class Node {  
  Object value;
  int color;
  Node left, right, parent;
}
//祖父節點
Node grandparent(Node n) {
  return n.parent.parent;
}
//叔叔節點
Node uncle(Node n) {
  if(n.parent == grandparent(n).left)
    return grandparent(n).right;
  else
    grandparent(n).left;
}複製代碼

插入場景1(變色):這個場景比較簡單,首次插入,根節點

void insert_case1(Node n){
  if(n.parent == NULL)    //根節點直接把顏色變黑就好了(變色)
    n.color = BLACK;
    return;
  else    //不然,進入場景2
    insert_case2 (n);
}複製代碼

插入場景2:父節點爲黑色,直接插入紅色節點就好了(新插入的節點是在原先的葉子節點,不會有兩個紅色相連的問題)

void insert_case2(Node n){
  if(n.parent.color == BLACK)
    return; //父節點爲黑色,直接插入紅色節點就好了(不用操做)
  else    //不然
    insert_case3 (n);
}複製代碼

插入場景3(變色):此時父節點存在且爲紅色,那麼祖父的節點確定是黑色。那麼要看uncle節點的狀況。假設uncle節點的顏色是紅色的

void insert_case3(Node n){
  //若是uncle節點的顏色是紅色的
  if(uncle(n) != NULL && uncle(n).color == RED) {
    n.parent.color = BLACK; //把父節點變黑
    uncle(n).color = BLACK; //uncle節點變黑
    grandparent(n).color = RED; //祖父節點變紅
    //此時祖父節點如下的都已經平衡了,剩下的就是要把祖父節點看成新插入的節點,去遞歸調用一下場景1開始的流程
    insert_case1(grandparent(n));
  }  else
    //若是uncle節點是黑色的,那麼看下場景4
    insert_case4 (n);
}複製代碼

場景3操做圖以下:


插入場景4(左旋或者右旋):此時父節點爲紅色,祖父節點是黑色,uncle節點只能爲NIL節點(由於uncle爲黑的話,就違反了特性5)

void insert_case4(Node n){
  //若是n爲右節點,n的父節點p爲祖父節點g的左節點,以下圖
  if(n == n.parent.right && n.parent == grandparent(n).left) {
    rotate_left(n.parent); //n的父節點執行--左旋操做
    //旋轉完成後,n指向本身的左節點(這樣就變成了場景5的一種情形:本身是左節點,且父節點是祖父節點的左節點)
    n = n.left;
  } else if(n == n.parent.left && n.parent == grandparent(n).right) {
    //若是本身是左節點,且父節點爲右節點
    rotate_right(n.parent); //n的父節點執行--右旋操做
    //旋轉完成後,n指向本身的右節點(這樣就變成了場景5的另外一種情形:本身是右節點,且父節點是祖父節點的右節點)
    n = n.right;
  }
  //場景4的操做結果,或者直接知足場景5的,都將進入場景5作最後的操做
  insert_case5 (n);
}複製代碼

場景4操做圖以下:



插入場景5:執行變色,以及祖父節點的旋轉

void insert_case5(Node n){
     //首先執行變色操做,把n的父節點變黑,n的祖父節點變紅
     n.parent.color = BLACK;
     grandparent(n).color = RED;
     //若是n爲左節點,n的父節點p爲n祖父節點G的左節點(如圖5-1)
     if(n == n.parent.left && n.parent == grandparent(n).left) {
         //n的祖父節點G右旋轉
         rotate_right(grandparent(n));
     } else {
         //n爲右節點,n的父節點p爲n的祖父節點G的右節點(如圖5-2)
         //n的祖父節點G左旋轉
         rotate_left(grandparent(n));
     }
 }複製代碼



      如5-1和5-2步驟圖所示,執行完畢後,從這棵子樹的根節點P到葉子節點的全部黑色節點數是2,與N插入前同樣(知足特性5),又由於P爲黑色,不會違反紅黑樹的其餘特性(特性1~4),因此,咱們插入的步驟就所有完成了。

刪除

      若是須要刪除的節點有兩個兒子,那麼問題能夠被轉化成刪除另外一個只有一個兒子的節點的問題,對於二叉查找樹,在刪除帶有兩個非葉子兒子的節點的時候,咱們要麼找到它左子樹中的最大元素、要麼找到它右子樹中的最小元素,並把它的值轉移到要刪除的節點中。剩下的就是變色和旋轉的操做了,篇幅有限,這裏就不往下說了。

相關文章
相關標籤/搜索