Java底層 RedBlackTree 紅黑樹

RedBlackTree 紅黑樹


1、2-3 樹

  紅黑樹和2-3書有着等價的關係。我們瞭解了紅黑樹之前,必須先了解2-3這種數據結構。

1.1、2-3 樹的基本結構

  2-3 樹也滿足樹的基本結構。但是一個節點可以存儲一個或者兩個元素。每一個節點可以有個或者個孩子。

基本結構

  下面是一種2-3樹的結構。排序的方式是根據字符的大小

2-3樹

  我們可以根據上面的結構來驗證2-3樹是否滿足樹的基本結構。
A->E->F->G->H->K->M->N->R->S->Z
  可以對樹結構進行中序遍歷來查看該結構是否具有順序性。我們可以得出一個結論就是:

2-3樹是一種絕對平衡的二叉樹

  可以看得出來任意節點的孩子節點的高度是相同的。下面就開始介紹這種數據結構是如何維持平衡性的。

1.2、2-3樹添加元素

  通過介紹如何添加元素來介紹這種樹結構是如何維持平衡性的。
下面以實例進行講解,這個地方主要還是靠大家的看。
添加的順序是:S->E->A->R->C->H->X->M->P

最關鍵的還是在於變換的操作。
  由於2-3樹的節點最多隻能存放2個元素,當有三個元素(四節點)的時候就需要進行變換。

節點分裂
  對於這種分裂並不會破壞樹結構的絕對平衡的性質,仍是一個絕對平衡的樹結構。對於新提出來的元素需要和父親節點進行融合。

2、紅黑樹與2-3樹的等價性

紅黑樹和2-3之間可以進行等價轉換。

等價轉換

  通過上面的等價轉換可以看出來,定義紅色節點都是向左傾斜。用過表示節點的顏色來識別該節點是否是紅色節點。
  對於我們上面的2-3樹我們可以對其進行轉換,如下圖:

紅黑樹和2-3樹轉換

根據上面的圖例,我們可以得出下面幾點結論:

  • 每一個節點是要麼是紅色的,要麼是黑色的
  • 每一個葉子節點(最後的空節點)均爲黑色的
  • 根節點爲黑色節點
  • 如果一個節點爲紅色的,那麼他的孩子節點均爲黑色的(否則觸發變換)
  • 從任意一個節點到達葉子節點,經過的黑色節點數是相同的(絕對平衡性)

解讀性質:

  • 對於性質一比較好理解,一個節點的顏色只有兩種狀態。
  • 對於性質二這裏的葉子節點並不是指左右子樹均爲空的節點,這個性質應該叫做定義,紅黑樹中定義空節點的顏色就是黑色,這裏和性質三相吻合
  • 對於性質三,和性質二是吻合的,對於一個空樹,它也是一種紅黑樹,那麼它既是葉子節點也是根節點,所以他的節點顏色也是黑色。
  • 對於性質四,可以從2-3樹中得出結論,紅色節點的產生於3節點,紅色節點連接的子節點來自於下一個2節點或者3節點,對於2節點來說,它本身的顏色就是黑色的,對於3節點而言,拆分成紅黑結構的時候,是黑色的節點連接父親節點。所以可以得出性質四
  • 對於性質五,可以通過紅黑樹的絕對平衡性得出,因此也可以拓展出一個結論,紅黑樹是一種保持黑平衡的的二叉樹

3、紅黑樹之增加元素

  由於紅黑樹和2-3具有等價的關係,所以添加元素主要分爲向2節點和三節點進行元素的添加,兩者對應不同的情況。

3.1、向 2節點中添加元素

  由於我們添加的節點都是紅色的節點,並且紅色節點一直保持左傾,對於下面這種情況,就需要進行一次旋轉操作。如果添加元素本身就是再左邊則無需變換。變換的操作就是和AVL樹一樣進行一次左旋轉操作,並且將顏色進行互換。如果新加入的節點本身就在左變則不需要進行變換。對應的函數爲 leftRotate()

增加元素

3.2、向 3節點中添加元素

  我們知道3節點添加位置主要包含三種,左邊、中間和右邊,下面逐一進行分析。
對於添加至右邊的情況:

添加至最右邊

  對於這種情況,只需要將左右孩子節點的顏色全部變成黑色即可,我們在前面說過,新生成的父親節點要去和上面的節點繼續去融合,融合就意味着該節點的顏色爲紅色,也是一種類似的顏色互換的形式。對應的函數稱之爲顏色反轉,對應的函數爲flipColor()

節點變換

對於添加至左邊的情況:

添加至最左邊
  這種情況就是對應的右旋轉過程,而且注意顏色的反轉變換。右旋轉過程和左旋轉類似,顏色也是進行互換。最後在進行一次顏色反轉操作。
右旋轉

對於添加至中間的情況:
  添加至中間的過程是一個組合的過程,可以先看成2節點的左旋轉過程和3節點的右旋轉過程。

左右旋轉

3.3、總結

  具體的添加元素操作可以直接使用下面這張圖,便於理解。

添加元素簡易圖

4、紅黑樹的實現

4.1、內部類的實現

  紅黑樹和二分搜索樹的節點函數大致相同,只不過增加了對節點顏色的標識。
節點內部類的實現:

private 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;   // 新建的節點永遠是融合節點,紅色節點
    }
}

4.2、基本結構以及函數實現

紅黑樹的基本架構程序實現:

public class RBTree<K extends Comparable<K>, V extends Comparable<V>>{

    private static final boolean RED = true;
    private static final boolean BLACK = false;
    private class Node{  
        //內部類 省略
    }
    private Node root;
    private int size;

    public RBTree(){
        root = null;
        size = 0;
    }
}

  對於紅黑樹這種結構,我們增加了對顏色的判斷,所有我們引入isRed函數來判斷這個節點是否是紅色的。
程序實現:

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

  基本輔助函數的實現,主要是對成員變量的一個get操作以及判斷紅黑樹是否爲空。
程序實現:

public int getSize() {
    return size;
}

public boolean isEmpty() {
    return size == 0;
}

4.3、增加元素

  我們根據上面的性質可以得到,根節點的顏色必須爲黑色,所以添加完元素後的根節點我們需要手動指定爲黑色,因爲我們新增的節點永遠是紅色的,所以我們需要手動將root變爲黑色,調用方式仍然是調用私有遞歸函數。

public void add(K key, V value) {
    root = add(root, key, value);
    root.color = BLACK; //手動將根節點變爲黑色
}

  在2節點中添加元素,並且在節點右邊添加元素會觸發左旋轉,進而保證紅色節點的左傾性。
左旋轉程序實現:

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;
}

  在3節點中添加元素涉及左中右三種情況,上面講過,在右邊添加元素觸發顏色反裝。
顏色反轉程序實現:

private void flipColors(Node node) {
    node.color = RED;
    node.left.color = BLACK;
    node.right.color = BLACK;
}

  在向三節點中最左邊添加元素,觸發右旋轉過程。右旋轉過程中還要進行一次顏色反轉。
右旋轉程序實現:

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;
}

  其他的添加元素的變換都是基於顏色變換,左右旋轉組合而來的。

  具體的調用方法和AVL樹相同,在增加完元素的時候,對元素進行判斷,是一個在回朔的過程中進行操作。
增加函數的程序實現:

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.key = key;

    //添加完成後,維護平衡性
    //左節點爲黑,右節點爲紅
    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;
}

5、時間複雜度分析

  從嚴格意義上講,紅黑樹並不是一種嚴格的平衡二叉樹。在最差的情況下,紅黑樹的高度爲2log(n),這是因爲在最差的情況下,每一個黑節點都有一個紅色節點,但是由於時間複雜度不考慮紅黑樹的時間複雜度也是O(log(n))級別的。

適應場景:
  紅黑樹適應於添加刪除元素頻繁的場景。而對於查詢元素來說,紅黑樹並不是嚴格意義上的平衡樹,在最壞的情況樹的高度可以達到2log(n),所以說不同的場景適應於不同的算法。但是綜合性能來說的話,紅黑樹的性能任然高於一些其他的樹結構。

最後

更多精彩內容,大家可以轉到我的主頁:曲怪曲怪的主頁

或者關注我的微信公衆號:TeaUrn

源碼地址:可在公衆號內回覆 數據結構與算法源碼 即可獲得。

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