1. 程式人生 > >java最優有序查詢——紅黑樹(RBT)演算法

java最優有序查詢——紅黑樹(RBT)演算法

在大量資料中常用的查詢資料的做法有四類:順序查詢,二分查詢,二叉樹查詢(BST),紅黑樹查詢(RBT)。
這四類查詢方法分別對應著四種基本思想原理:

順序查詢 —— 無序簡單查詢

二分查詢 —— 有序查詢,每次折半搜尋,插入資料費時

二叉樹查詢(BST) —— 不平衡二叉樹有序查詢,插入與搜尋綜合性能較優

紅黑樹查詢(RBT) —— 平衡二叉樹有序查詢,插入與搜尋綜合性能最優

注:從上至下平均效能變優演算法難度增大,後三種查詢為有序查詢。

紅黑樹(RBT)效能分析

在編寫紅黑樹實現前很有必要對其效能進行分析,通過與前三種演算法的比較,分析出現RBT這種資料結構的原因。

演算法 最壞查詢 最壞插入 平均查詢 平均插入 是否有序
順序查詢 N N N/2 N
二分查詢 lgN N lgN N/2
二叉樹查詢(BST) N N 1.39lgN 1.39lgN
紅黑樹查詢(RBT) 2lgN 2lgN 1.001lgN 1.001lgN

順序查詢

順序查詢是指對資料進行無序的從頭至尾掃描,這種方法是最原始的最壞情況下查詢目標資料需要遍歷整個陣列,平均查詢需要遍歷半個陣列。而插入則需要完全遍歷整個陣列。

二分查詢

二分查詢是查詢時間最短的方法,將陣列每次以中間值判斷,穩定查詢lgN(注:lg = log2)時間。但二分查詢的插入則需要花費N的複雜度級別的時間來完成。如果我們採用類似二分查詢的方式進行插入,即:每次以中值判斷最後找出要插入的點,這樣看起來時間也是lgN,但對於陣列而言插入意味著將之後的數字依次移位,這樣更加得不償失。如果你採用連結串列結構來實現二分查詢,可以滿足快速的插入,但是由於沒有了索引,仍需遍歷陣列得到索引,所以二分查詢的插入操作複雜度級別為N

二叉樹查詢(BST)

二叉樹查詢是在二分基礎上提出,為了解決二分查詢的插入問題的一種新的查詢模式,這種模式通過類似連結串列的樹結構

不斷地插入資料達到有序,而其查詢時間也在lgN複雜度左右,程式碼實現較為簡單。在整個有序樹中存在著固定的插入和查詢的模式,最後樹的形狀受到插入資料的先後順序影響。正因為樹的形狀受到隨機因素影響,在最壞的情況下,這棵樹只有左子樹沒有右子樹(或只有右子樹沒有左子樹)導致效能甚至不如二分查詢。

紅黑樹查詢(RBT)

紅黑樹查詢是在二叉樹查詢的基礎上,結合了二叉樹中簡潔高效的查詢方法,又同時是一顆保證效能平衡樹。在最壞的情況下,紅黑樹都遠好於二叉樹。在平均情況下的搜尋,紅黑樹理論上只比二分查詢差一點,這是因為紅黑樹資料結構的特性導致,我們可以認為紅黑樹的查詢和二分查詢是相同的。在插入的複雜度比較中比二分查詢好太多,紅黑樹的演算法思路是所有有序查詢中最優的演算法,用便於實現的平衡樹高效地完成插入和搜尋操作。

紅黑樹定義

滿足下列條件的二叉樹是紅黑樹:

  1. 紅連結均為左連結
  2. 沒有任何一個結點同時和兩條紅連結相連
  3. 該樹完美黑色平衡,即任意葉結點到根結點的路徑上的黑連結數量相同

紅黑樹(RBT)基本結點

package Tree;

public class RBT<Key, Value> {

    private static final boolean RED = true;
    private static final boolean BLACK = false;
    private class Node {
        Key key;
        Value val;
        Node left, right;
        int N;
        boolean color;

        Node(Key key, Value val, int N, boolean color) {
            this.key = key;
            this.val = val;
            this.N = N;
            this.color = color;
        }
    }

    private boolean isRed(Node x) {
        if (x == null)
            return false;
        return x.color == RED;
    }
}

用boolean型別color變量表示該結點到其父節點的顏色。

RED結點左右旋轉

  private Node rotateLeft(Node h) {
        Node x = h.right;
        h.right = x.left;
        x.left = h;
        x.color = h.color;
        h.color = RED;
        x.N = h.N;
        h.N = size(h.left) + size(h.right) + 1;
        return x;
    }

    private Node rotateRight(Node h) {
        Node x = h.left;
        h.left = x.right;
        x.right = h;
        x.color = h.color;
        h.color = RED;
        x.N = h.N;
        h.N = h.left.N + h.right.N + 1;
        return x;
    }

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

新結點插入規則

在紅黑樹中插入一個新結點要遵循下面的規則:

  1. 任何插入的結點的顏色都設為紅色(該結點到父接點的連結為紅色)
  2. 整棵樹的根節點始終設為黑色
  3. 如果一個結點的右結點連結為紅色,則進行左旋轉
  4. 如果一個結點與左右子結點都為紅連結,則將其與左右兩個子結點連結變為黑色,該結點到父結點顏色變為紅色
  5. 如果一個結點的左結點為紅,左結點的左結點也為紅,將原結點進行一次右旋轉,然後執行第3條
  6. 如果一個結點的左結點為紅,左結點的右結點也為紅,將原結點的左結點進行一次左旋轉,然後執行第4條

第1、2條表示了在進行插入旋轉調整時要遵循的規定,第3條表示在簡單插入不滿足紅連結均為左連結的定義時進行的操作。第4、5、6條表示不滿足一個結點同時和兩條紅連結相連的情況時進行的操作,這三種情況涵蓋了所有可能發生的錯誤情況。

新結點程式碼中調整步驟

上述插入規則看起來非常複雜,但是隻要我們在程式碼中依次進行這三步檢查和調整,就可以避免所有錯誤情況的發生:

  1. 如果右子結點是紅色的而左子結點是黑色的,左旋轉。
  2. 如果左子結點是紅色的且它的左子結點也是紅色的,右旋轉。
  3. 左右子結點均為紅色,進行顏色轉換。

複雜的邏輯通過這三步判斷和檢查會被處理的完全正確。分析一下這三步操作的核心思想,第一步為了將所有可以放在左邊的紅色右結點左移,這是最簡單的操作,讓儘量多的紅結點左移可以讓上面插入規則中第6條退化為第5條,第3條直接解決。完成第一步調整後只存在4和5兩條的情況,第二步的兩條連續左紅調整是為了讓第5條退化為第4條。第三步的顏色轉化針對第4條情況。所以,3、4、5、6這四條錯誤情況在這三步過程中完全被解決。

紅黑樹(RBT)插入演算法

public void put(Key key, Value val) {

    root = put(root, key, val);
    root.color = BLACK;

}

private Node put(Node root, Key key, Value val) {

    if (root == null)
        return new Node(key, val, 1, RED);

    int comp = key.compareTo(root.key);

    if (comp < 0)
        root.left = put(root.left, key, val);
    else if (comp > 0)
        root.right = put(root.right, key, val);
    else
        root.val = val;

    if (isRed(root.right) && !isRed(root.left))
        root = rotateLeft(root);
    if (isRed(root.left) && isRed(root.left.left))
        root = rotateRight(root);
    if (isRed(root.left) && isRed(root.right))
        flipColors(root);

    root.N = size(root.left) + size(root.right) + 1;

    return root;
}

紅黑樹(RBT)刪除演算法

紅黑樹(RBT)刪除演算法策略:從根節點開始,在遞迴的過程中,通過結點的變換讓每個當前結點都是一個非單獨結點,即該結點上至少有一個紅連結。這樣可以保證在刪除的時候不會直接刪除一個單獨的黑連結而導致樹不平衡,這樣確實會使得整棵樹的紅黑性變得混亂,可能出現一個結點上連線了多個紅連結的情況,但是我們最後會通過balance方法遞迴的整理所有的結點顏色,確保正確性。

為了便於描述,我們將一個紅連結連線的兩個結點看成是一個包含兩個結點的大結點,也稱之為2結點,即包含2個結點的結點。因為刪除2結點中的任意一個結點不會影響紅黑樹的平衡性,但如果我們刪除了沒有任何紅連結的結點(也就是1結點)則肯定會影響平衡性。上段中說,在遞迴的過程中要通過結點的變換讓每個當前結點至少有一個紅連結(至少為2結點,也可以是3結點或更多),為了達到這個目的,我們有三種情況。

  1. 如果當前結點的左子結點不是1結點,完成。
  2. 如果當前結點的左子結點是1結點而它的親兄弟節點不是1結點,將左子結點的兄弟結點中的一個結點移動到左子結點中。
  3. 如果當前結點的左子結點和它的親兄弟結點都是1結點,將左子結點、父結點中最近的結點(因為我們保證了每個結點都不是1結點,所以父節點必不是1結點)、左子結點最近的兄弟結點合併為一個3結點,而父結點從2結點變為1結點或從3結點變為2結點。

這三種方法都可以達到我們的目的,但是更具體的,3的做法比2的做法對整棵樹的影響更大,所以我們優先按照1、2、3的順序來進行結點的變換。

刪除演算法所需的輔助函式

  
    /**
     * 此函式用於把父節點變為黑色,兩個子結點變為紅色,與flipColors函式功能正好相反
     * 可以理解成是將三個結點(一個父結點和兩個子結點)合併成含三個結點的大結點
     * 
     * @param h
     */
    private void reFlipColors(Node h) {
        h.color = BLACK;
        h.left.color = RED;
        h.right.color = RED;
    }

    /**
     * 獲取以root為根的最小結點,就是整棵樹最左邊的結點
     * 
     * @param root
     * @return
     */
    private Node min(Node root) {

        if (root.left == null)
            return root;

        return min(root.left);

    }

    /**
     * 獲取相對應key的結點
     * 
     * @param root
     * @param key
     * @return
     */
    private Value get(Node root, Key key) {

        if (root == null)
            return null;

        int comp = key.compareTo(root.key);

        if (comp == 0)
            return root.val;
        else if (comp < 0)
            return get(root.left, key);
        else
            return get(root.right, key);

    }

    /**
     * 將h結點按照上面調整的三條步驟進行調整
     * 
     * @param h
     * @return
     */
    private Node balance(Node h) {

        if (isRed(h.right))
            h = rotateLeft(h);
        if (isRed(h.right) && !isRed(h.left))
            h = rotateLeft(h);
        if (isRed(h.left) && isRed(h.left.left))
            h = rotateRight(h);
        if (isRed(h.left) && isRed(h.right))
            flipColors(h);
        h.N = size(h.left) + size(h.right) + 1;

        return h;
    }

刪除最小值演算法——DeleteMin

刪除最小值的演算法思路是:一路向左判斷,如果該結點的”左子結點”和”左子結點的左子結點”都是1結點則採用3種變換方式構建2結點或3結點。刪除最小值演算法給出了每一步的註釋,刪除最大值演算法和刪除演算法具體的步驟含義留給大家自己思考。

  private Node moveRedLeft(Node h) {

        reFlipColors(h);
        //讓父結點為黑連結,兩個子結點為紅連結
        //即將1結點變為3結點——上文中的第3步做法

        if (isRed(h.right.left)) {
            h.right = rotateRight(h.right);
            h = rotateLeft(h);
        }
        //如果原結點的右結點的左結點是紅色
        //表示原結點的左子結點的兄弟結點是2結點或3結點
        //採用上文第2步做法,借一個結點,將左子結點變化為2結點
        //這裡不需要將reFlipColors的顏色變動改回
        //reFlipColors的顏色變動在最後的balance方法會被調整

        return h;

    }

    public void deleteMin() {

        if (!isRed(root.left) && !isRed(root.right))
            root.color = RED;
        //如果根結點的兩個子結點都為黑色,則將根結點暫時設定為紅色
        //這樣可使根結點不為1結點,便於遞迴

        root = deleteMin(root);

        if (root != null)
            root.color = BLACK;
        //將根結點顏色換回黑色,滿足紅黑樹定義

    }

    private Node deleteMin(Node h) {

        if (h.left == null)
            return null;
        //遞迴結束點,找到了要刪除的左結點,置為null

        if (!isRed(h.left) && !isRed(h.left.left))
            h = moveRedLeft(h);
        //如果"左子結點"和"左子結點的左子結點"都為黑色則呼叫函式
        //呼叫moveRedLeft,使得左子結點變成2結點或3結點

        h.left = deleteMin(h.left);

        return balance(h);
        //調整整棵樹的紅黑顏色情況,使其滿足紅黑樹定義
    }

刪除最大值演算法——DeleteMax

刪除最大值和最小值並不是left和right交換的簡單情況,因為紅黑樹的紅連結僅能出現在左邊,所以刪除最大值(刪除最右邊結點)的演算法有些不同。但是還是按照上面的三條情況來處理結點。原理上是相同的。

  private Node moveRedRight(Node h) {

        reFlipColors(h);

        if (isRed(h.left.left))
            h = rotateRight(h);

        return h;
    }

    public void deleteMax() {

        if (!isRed(root.left) && !isRed(root.right))
            root.color = RED;

        root = deleteMax(root);

        if (root != null)
            root.color = BLACK;

    }

    private Node deleteMax(Node h) {

        if (isRed(h.left))
            h = rotateRight(h);

        if (h.right == null)
            return null;

        if (!isRed(h.right) && !isRed(h.right.left))
            h = moveRedRight(h);
        h.right = deleteMax(h.right);

        return balance(h);
    }

刪除演算法

刪除演算法非常複雜,但是我們前面做了足夠的鋪墊,所以總程式碼量看起來將不會太多。刪除演算法的策略是:按照key和當前結點的key比較,進行刪除最小值的操作或刪除最大值的操作,當相等的時候進行二叉查詢樹中替換後繼結點的做法。最後用balance方法保持節點顏色正確。注意,刪除的結點要在樹中存在。

  public void delete(Key key) {

        if (!isRed(root.left) && !isRed(root.right))
            root.color = RED;

        root = delete(root, key);

        if (root != null)
            root.color = BLACK;

    }
    private Node delete(Node h, Key key) {

        if (key.compareTo(h.key) < 0) {
            if (!isRed(h.left) && !isRed(h.left.left))
                h = moveRedLeft(h);
            h.left = delete(h.left, key);
        } else {
            if (isRed(h.left))
                h = rotateRight(h);

            if (key.compareTo(h.key) == 0 && (h.right == null))
                return null;
            if (!isRed(h.right) && !isRed(h.right.left))
                h = moveRedRight(h);
            if (key.compareTo(h.key) == 0) {
                h.val = get(h.right, min(h.right).key);
                h.key = min(h.right).key;
                h.right = deleteMin(h.right);
            } else
                h.right = delete(h.right, key);
        }

        return balance(h);
    }

紅黑樹其他操作演算法

除去插入和刪除演算法,紅黑樹的其他演算法與二叉查詢樹演算法完全相同,因為紅黑樹本身就是一顆二叉樹,只不過在黑色連結上具有平衡樹的特性而更高效。可以發現,在插入操作中,查詢位置替換和插入新結點部分和二叉樹插入是完全相同的,在刪除部分查詢刪除鍵和後繼結點交換也是和二叉樹相同的。

相關推薦

java有序查詢——RBT演算法

在大量資料中常用的查詢資料的做法有四類:順序查詢,二分查詢,二叉樹查詢(BST),紅黑樹查詢(RBT)。 這四類查詢方法分別對應著四種基本思想原理: 順序查詢 —— 無序簡單查詢 二分查詢 —— 有序查詢,每次折半搜尋,插入資料費時

二叉查詢BST | 平衡二叉查詢AVL | RBT

二叉查詢樹(BST) 特點:對任意節點而言,左子樹(若存在)的值總是小於本身,而右子(若存在)的值總是大於本身。 查詢:從根開始,小的往左找,大的往右找,不大不小的就是這個節點了; 插入:從根開始,小的往左,大的往右,直到葉子,就插入, 時間複雜度期望為

查詢演算法6RBT

前言:        2-3樹雖然能實現平衡性,但在插入和刪除的過程中需要判斷插入的節點是2-節點還是3-節點等一系列問題,實現複雜且會增加額外的開銷,所以就提出了紅黑樹(發明者--Sedgewick,1987年)一.基本概念 1. R-B Tree,全稱是Red-Black

數據結構Java版之

如何 當前 鏈接 根節點 java版 -- 查找 變色 繼承   紅黑樹是一種自動平衡的二叉查找樹,因為存在紅黑規則,所以有效的防止了二叉樹退化成了鏈表,且查找和刪除的速度都很快,時間復雜度為log(n)。   什麽是紅黑規則?   1.根節點必須是黑色的。   2.節點顏

data_structure_and_algorithm -- :為什麼工程中都用這種二叉

今天主要看一下紅黑樹,主要參考:前谷歌工程師王爭的課程,感興趣可以通過下面方式微信掃碼購買: 樹、二叉樹、二叉查詢樹。二叉查詢樹是最常用的一種二叉樹,它支援快速插入、刪除、查詢操作,各個操作的時間複雜度跟樹的高度成正比,理想情況下,時間複雜度是 O(logn)。

從2-3-4

歡迎探討,如有錯誤敬請指正 相關部落格: 1. 2-3-4樹的定義 2-3-4樹是一種階為4的B樹。它是一種自平衡的資料結構,可以保證在O(lgn)的時間內完成查詢、插入和刪除操作。它主要滿足以下性質: (1)每個節點每個節點有1、2或3個key,分別稱為2(孩子)節點,3(孩子)節點,4(孩子)節點。

從2-3-4

歡迎探討,如有錯誤敬請指正 相關部落格: 1. 紅黑樹的定義 2-3-4樹和紅黑樹是完全等價的,由於絕大多數程式語言直接實現2-3-4樹會非常繁瑣,所以一般是通過實現紅黑樹來實現替代2-3-4樹,而紅黑樹本也同樣保證在O(lgn)的時間內完成查詢、插入和刪除操作。 紅黑樹是每個節點都帶有顏色屬性的平衡二

2保持平衡的根本套路

有了解過的同學們應該都知道,紅黑樹為了保持平衡,有三個基本的操作:左旋、右旋和著色。(不知道的同學也沒關係 ,因為在此我只想告訴你們的是這三個操作都是為了什麼) 首先要有這樣一個意識:要棵紅黑樹在你插入新節點之前就是平衡的(不理解紅黑樹平衡的可看上一節),我們要做的是使

1真的只說原理

樹可以分為兩大類:平衡的樹和不平衡的樹。 那些都有自己大名的自然就是平衡的樹。我們使用樹這結構就是看中它插刪與搜尋擁有同等的高效 O(log n)。 平衡樹的特點就是每層都幾乎佈滿,不會出現某一分支特別長的情況。因為樹的查詢跟層數有關,層數越大越耗時間。如果有10個節點就

3殘疾版新增實現

為什麼說是殘疾版呢,因為標準的紅黑樹是是三個值在一層,也就是父節點的左右分支節點都可以是紅,但在此,我規定了只有左分支為紅,也就是規定了最多隻有兩個值在一層。這樣能減少很多修復平衡判斷條件。在此我以實現簡化版的treeMap 為例。 新增節點的第一步就是找出節點將要加入的位

linux核心分析--核心中的資料結構之

#include<linux/rbtree.h> #include <linux/string.h> #include "kn_common.h" MODULE_LICENSE("Dual BSD/GPL"); struct student { int id;

linux核心分析--核心中的資料結構之

紅黑樹由於節點顏色的特性,保證其是一種自平衡的二叉搜尋樹。 紅黑樹的一系列規則雖然實現起來比較複雜,但是遵循起來卻比較簡單,而且紅黑樹的插入,刪除效能也還不錯。 所以紅黑樹在核心中的應用非常廣泛,掌握好紅黑樹,即有利於閱讀核心原始碼,也可以在自己的程式碼中借鑑這種資料結構。 紅黑樹必

資料結構之——插入操作

插入或刪除操作,都有可能改變紅黑樹的平衡性,利用顏色變化與旋轉這兩大法寶就可應對所有情況,將不平衡的紅黑樹變為平衡的紅黑樹。 在進行顏色變化或旋轉的時候,往往要涉及祖孫三代節點:X表示操作的基準節點,P代表X的父節點,G代表X的父節點的父節點。 我們先來大體預覽一下插入的

演算法】手撕—— 基本性質以及插入實現附帶程式碼實現

在閱讀其他博主關於紅黑樹增刪實現的時候,博主們大多直接使用文字圖片描述,對整個增刪整體的流程突出的不太明顯(當然dalao們寫得還是很棒得,不然我也寫不出這篇文章),所以我特意花了2天時間用CAD製作了 一張插入操作的流程圖和一張刪除操作的流程圖(刪除見下篇)並手撕程式碼(好吧,其實大部分時間在除錯程式碼,畢

查詢史上簡單清晰的講解

查詢(一) 我們使用符號表這個詞來描述一張抽象的表格,我們會將資訊(值)儲存在其中,然後按照指定的鍵來搜尋並獲取這些資訊。鍵和值的具體意義取決於不同的應用。 符號表中可能會儲存很多鍵和很多資訊,因此實現一張高效的符號表也是一項很有挑戰性的任務。 我們會用三種經典的資料型

查找史上簡單清晰的解說

ont 演示 detail align article 向上 節點 動態插入 列表 查找(一) 我們使用符號表這個詞來描寫敘述一張抽象的表格。我們會將信息(值)存儲在當中,然後依照指定的鍵來搜索並獲取這些信息。鍵和值的詳細意義取決於不同的應用。 符號表中可能會保

數據結構 - Red Black Tree插入詳解與實現Java

啟示 dpa con 技術分享 節點數 src 通知 一點 this   最終還是決定把紅黑樹的篇章一分為二,插入操作一篇,刪除操作一篇,因為合在一起寫篇幅實在太長了,寫起來都覺得累,何況是閱讀並理解的讀者。       紅黑樹刪除操作請參考 數據結構 - 紅黑樹(Red

數據結構 - Red Black Tree刪除詳解與實現Java

replace ati 轉載 之前 9.png one com 四種 簡單   本篇要講的就是紅黑樹的刪除操作       紅黑樹插入操作請參考 數據結構 - 紅黑樹(Red Black Tree)插入詳解與實現(Java)   紅黑樹的刪除是紅黑樹操作中比較麻煩且比較有意

Red Black Tree刪除詳解與實現Java

  本篇要講的就是紅黑樹的刪除操作   紅黑樹的刪除是紅黑樹操作中比較麻煩且比較有意思的一部分。   在此之前,重申一遍紅黑樹的五個定義: 1. 紅黑樹的節點要不是黑色的要不是紅色的     2. 紅黑樹的根節點一定是黑色的     3. 紅黑樹的所有葉子節點都是黑色的(注意:紅黑樹的葉子節點指Nil節點

Java中的TreeMap與

文章目錄 TreeMap的有序指的是什麼?怎麼實現有序?用的什麼資料結構? 為什麼說紅黑樹是AVL? 紅黑樹怎麼維持平衡? 插入和刪除哪個簡單? 還有哪些集合使用紅黑樹? TreeMap的有序指的是什麼