數據結構--紅黑樹

紅黑樹

定義

  紅黑樹也是一種平衡搜索樹,他能夠保證在最壞的狀況下基本動態集合操做的時間複雜度爲O(lgn)。java

  紅黑樹是一棵二分搜索樹,它在每一個位置上增長了一個存儲位來表示節點的顏色,能夠是RED或BLACK。經過任何一條從根到葉子的簡單路徑上各個結點的顏色進行約束,紅黑樹確保沒有一條路徑會比其餘路徑常出兩倍,於是近似於平衡的。node

紅黑樹與 2-3查找樹

  紅黑樹是2-3查找樹的一種表示方式。區別在於,2-3查找樹中包含有2-結點3-結點。在紅黑樹中只有全部的結點都是2-結點,這樣一來就會有這樣的疑問,既然都是2-結點,那怎麼表示2-3樹呢? 答案就是,在紅黑樹中,對2-3樹中的3-結點作了必定的處理,經過使用2-結點和一些其餘的信息來表示3-結點git

紅黑樹中對2-3樹中的3-結點的處理github

  將2-3樹中的3-結點拆分紅2個2-結點,並將這兩個2-結點的左節點使用紅色的連接,右鏈接使用黑色(原來2-3樹中的連接)連接,連在一塊兒。以下圖:算法

這樣一來,就獲得了構造紅黑樹的基本思想:bash

  用標準的二分搜索樹(徹底由2-結點構成)和一些額外的信息(替換3-節點)來表示2-3樹微信

從上面的描述咱們能夠獲得紅黑樹的定義:學習

  紅黑樹是含有紅黑連接並知足下列條件的二分搜索樹:ui

   1. 紅連接均爲左連接;this

   2. 沒有任何一個結點同時和兩條紅連接相連;

   3. 該樹是完美平衡的,即任意空連接到根結點的路徑上黑連接的數量相同。

紅黑樹中鏈接的分類:

  1. 紅連接將兩個2-結點鏈接起來構成一個3-結點;

  2. 黑連接則是2-3樹中的普通連接。

以下圖所示:

紅黑樹中顏色替換

  爲了方便表示紅黑樹,間紅黑樹中連接的顏色表示在該連接所鏈接的結點中,咱們都知道,一條連接的兩端,有兩個結點(父子結點),將鏈接的顏色保存在子節點中。作顏色替換後,紅黑樹的示意圖以下:

上面圖片左側的部分,在連接的旁邊都有一個數字,這個數字稱爲:黑高

黑高(black-height)定義

   從某個結點出發(不含該結點)到達一個葉子結點的任意一條簡單路徑上的黑色連接(結點)的個數。

   經過上述的黑高定義,及紅黑樹的平衡性能夠知道紅黑樹的黑高就是根結點的黑高

紅黑樹的操做

紅黑樹的構成:

  • 節點   樹中的結點包含5個屬性:color、key、left、right、和p。

java代碼:

public class Node{
      /** 鍵*/
      public K key;
      /** 相關聯的值*/
      public V value;
      /** 左右子樹*/
      public Node left,right;
      /** 父結點指向該結點的顏色*/
      public boolean color;
      public Node(K key, V value){
          this.key = key;
          this.value = value;
          left = null;
          right = null;
          color = RED;
      }
  }
複製代碼
紅黑樹圖示:

紅黑樹的性質

  • 1.每一個節點或是紅色的,或是黑色的;

  • 2.根結點是黑色的;

  • 3.每一個葉子結點(空連接)是黑色的;

  • 4.若是一個結點是紅色的,則它的兩個葉子結點都是黑色的;

  • 5.對每一結點,從該結點到其全部後代葉結點的簡單路徑上,均包含有相同數目的黑色結點。

旋轉

  在對紅黑樹進行操做(插入或刪除)的時候,可能會出現右連接爲紅色連接,或者有兩條連續的紅色連接。這樣一來就破壞紅黑樹的性質。所以須要 在本次操做完成以前經過旋轉使得本次操做後,該樹仍然是紅黑樹。旋轉操做會改變紅連接的指向。旋轉也分爲左旋轉右旋轉

  左旋轉:將紅色鏈接爲右連接轉化爲左連接。圖示以下:

總結:

  上述圖片中,結點值爲8的結點顏色能夠是紅色,夜能夠是黑色。該結點一樣也但是左子樹,也能夠是右子樹。上述左旋轉的過程:將上述圖片的部分當作一棵子樹,該子樹的根結點的值是8。通過旋轉後將值爲15的結點做爲該樹的根結點。(將兩個鍵中值較小的左爲跟結點變成值交大者做爲根結點的過程)。

  • 左旋轉的代碼實現:
private Node leftRotate(Node node){
    Node x = node.right;
    //左旋轉
    node.right = x.left;
    x.left = node;

    x.color = node.color;
    node.color = RED;
    return x;
}
複製代碼

  右旋轉:將紅色鏈接爲左連接轉化爲右連接。圖示以下:

總結:

  紅黑樹右旋轉的過程就是左旋轉的逆向過程。

  • 右旋轉的代碼實現:
private Node rightRotate(Node node){
    Node x = node.left;
    node.left = x.right;
    x.right = node;

    x.color = node.color;
    node.color = RED;
    return x;
}
複製代碼

插入

  爲了更好的理解紅黑樹,在上一篇文章中首先學習了2-3樹,這一篇文章中關於紅黑樹的學習,也是經過2-3樹通過相應的變化而來。這裏要對紅黑樹 進行插入結點的操做,一樣類比2-3樹的插入。來看看,在紅黑樹的插入過程當中,是如何維護紅黑樹的性質的。

  在紅黑樹的插入操做中,假設插入的節點都爲紅色。

這裏假設結點爲紅色,插入紅黑樹以後,會破壞上述的性質4。經過上述的旋轉過程能夠進行相應的調整來維護紅黑樹。這裏若是假設插入的是黑色的結點,就會破壞紅黑樹的平衡性(性質5)。

向2-結點中插入新鍵

  向2-結點中插入新鍵分爲以下的兩種狀況。

紅黑樹根結點的左側插入

  在紅黑樹的根結點的左側插入,默認插入是紅結點,而紅黑樹根結點爲黑節點,插入該結點後,紅黑樹的性質不變。

紅黑樹根結點的右側插入

  在紅黑樹的根結點的右側插入,因爲默認插入的是紅色結點,插入後不知足紅黑樹的性質,此時右節點爲紅色結點。經過左旋轉,將其旋轉爲左結點爲紅色結點,修正根結點的鏈接。

總結:

  通過上述操做後,該紅黑樹等價爲一棵只有一個3-結點的2-3樹,該紅黑樹有兩個結點,其中一個爲紅色結點,樹的黑高爲1。

向樹底部的2-結點中插入新鍵

  用和二分搜索樹相同的方式向一棵紅黑樹中插入一個新鍵會在樹的底部新增一個結點(保證樹的有序性),一樣也是紅結點和其父結點相連。若是父結點是一個2-結點,那麼上述的兩種處理方式仍然適用。若是指向新結點的是父結點的左連接,那麼父結點就直接成爲一個3-結點;若是指向新結點的是父結點的右連接,這就是一個錯誤的3-結點,經過一次左旋轉來修正。

向一棵雙鍵樹(3-結點)中插入新鍵

  雙鍵樹中插入新的鍵分爲以下的三種狀況,下面分類討論:

新插入的鍵大於原樹中的兩個鍵

  要插入的鍵在大於原樹中的兩個鍵,此時根結點連接兩個紅結點,不符合紅黑樹的性質,只需將兩個紅色節點變爲黑色節點便可。

新插入的鍵介於原樹中的兩個鍵之間

  新插入的鍵介於原樹兩個鍵之間,這會產生兩個連續的紅色結點,一個結點是左結點,一個結點是右結點。須要將紅色的右結點先左旋轉變,使其變成兩個連續的紅色結點;而後在進行右旋轉使其變成一個結點連接兩個紅色的結點(一左一右);最後在對兩個紅色結點的顏色進行變換。

新插入的鍵小於原樹中的兩個鍵

  新插入的鍵小於原樹中的兩個鍵,新插入的結點會被連接到最左邊的空連接,這樣也產生了兩個連續的紅色結點,將從根結點開始的第一個紅結點右旋轉,使根結點連接兩個紅色的結點;而後,將兩個紅色結點的顏色變爲黑色。

顏色轉換

  當一個結點連接兩個紅色的子結點時,須要將子結點的顏色右紅變黑,同時也須要將父結點的顏色由黑變紅。

代碼實現

private void flipColors(Node node){
    node.color = RED;
    node.left.color = BLACK;
    node.right.color = BLACK;
}
複製代碼
根結點老是黑色的

  在發生顏色轉換的時候,會遇到根結點連接連個紅色結點的狀況。此時進行顏色轉換後根結點爲紅色,當紅色及結點出如今根結點的時候,紅黑樹的黑高就會增長1。上篇文章中有說到,2-3樹的節點與紅黑樹結點的對應關係。而紅色結點的來源就是3-結點拆成兩個2結點,而後將左2-結點標記爲紅色。在每次添加新的結點後,都將紅黑樹的根結點設置爲黑色。

向樹底部的3-結點中插入新鍵

  假設在樹的底部的一個**3-**結點下加入一個新的結點。上面討論的三種狀況都會出現。指向新結點的連接多是3-結點的右連接(此時須要轉換顏色),或是左連接(須要右旋轉而後再轉換顏色),或是中連接(須要先左旋轉下層連接而後右旋轉上層連接,最後再轉換顏色)。顏色轉換會使到中結點的連接變紅,至關於將它送入了父結點。這意味着在父結點中繼續插入一個新鍵,使用相同的辦法解決這個問題。

將紅連接在樹中向上傳遞

  2-3樹中插入算法須要咱們分解3-結點,將中間鍵插入父結點,如此這般直遇到一個2-結點或根結點。以前考慮過的全部狀況都是爲了達成這個目標:每次必要的旋轉以後咱們都會進行顏色轉換 使得根結點變紅。站在父結點的角度來看,處理這樣的一個紅色結點的方式和處理一個新插入的紅色結點徹底相同,繼續把紅連接轉移到中間結點上去。下圖中總結的三種狀況顯示了在紅黑樹實現2-3樹的插入算法的關鍵操做所需步驟:要在一個3-結點下插入新鍵,先建立一個臨時的4-結點,將其分解並將紅連接由中間鍵傳遞給它的父結點。重複這個過程,咱們就能將紅連接在樹中向上傳遞,直至遇到一個2-結點或根結點。

總結:

  只要謹慎的使用左旋轉,右旋轉和顏色轉換這三個簡單的操做,就能夠保證插入操做後紅黑樹和2-3樹的一一對應關係。在沿着插入點到根結點的路徑向上移動時在所通過的每一個結點中順序完成如下操做,便可完成插入操做

  • 若是右子結點是紅色的而左子結點是黑色的,進行左旋轉;

  • 若是左子結點是紅色的且它的左子結點也是紅色的,進行有旋轉;

  • 若是左右子結點均爲紅色,進行顏色轉換。

紅黑樹的插入代碼實現

public void add(K key, V value){
     root = add(root, key, value);
     root.color = BLACK;
 }
複製代碼
private Node add(Node node,K key, V value){
    if(node == null){
        size++;
        //默認插入紅色節點
        return new Node(key,value);
    }
    if(key.compareTo(node.key) < 0){
        node.left = add(node.left,key,value);
    }else if(key.compareTo(node.key) > 0){
        node.right = add(node.right,key,value);
    }else{
        node.value = value;
    }
    if(isRed(node.right) && !isRed(node.left)){
        node = leftRotate(node);
    }
    if(isRed(node.left) && isRed(node.left.left)){
        node = rightRotate(node);
    }
    if(isRed(node.left) && isRed(node.right)){
        flipColors(node);
    }
    return node;
}
複製代碼

  上述代碼中的isRed()方法用來判斷結點的顏色。

private boolean isRed(Node node){
    if(node == null){
        return BLACK;
    }
    return node.color;
}
複製代碼

我的微信公衆號:

我的github:

github.com/FunCheney

相關文章
相關標籤/搜索