紅黑樹

前言

從jdk8.0開始HashMap進行了一次革新焚虱,為了提高查找效率在哈希桶內(nèi)元素超過8個時自動變?yōu)榧t黑樹結(jié)構(gòu)。

紅黑樹到底是為了解決什么問題存在的沾鳄?它又是怎么出現(xiàn)的翔烁?我們首先要理解一顆二叉查找樹辉哥。

二叉查找樹或者是一棵空樹兜喻,或者是具有下列性質(zhì)的二叉樹:
(1)若左子樹不空暇赤,則左子樹上所有結(jié)點的值均小于或等于它的根結(jié)點的值匀油;
(2)若右子樹不空稠炬,則右子樹上所有結(jié)點的值均大于或等于它的根結(jié)點的值空镜;
(3)左杏糙、右子樹也分別為二叉查找樹贩耐;


.
當然如果要說二叉查找樹的話我們必須從符號表說起耻瑟,可我們今天的目的是為了簡述紅黑樹旨指,所以二叉查找樹的各種實現(xiàn)就當大家都已經(jīng)懂得。

那為什么會出現(xiàn)紅黑樹呢喳整,我們發(fā)現(xiàn)一種情況當輸入的鍵值對鍵值按升序或者降序插入的時候谆构,會有下圖的情況出現(xiàn)


紅黑樹.png

而這種情況也是最糟糕的情況假設(shè)我們要查找鍵為5的結(jié)點,那么花費的時間就是最長的了框都,我們發(fā)現(xiàn)其實查找時間其實跟樹的高度應(yīng)該是呈現(xiàn)一種正比的關(guān)系搬素。
那么可能有些同學想到了平衡二叉查找樹,也就是任何節(jié)點的兩個子樹的高度最大差別為一。但是我們發(fā)現(xiàn)一個問題熬尺,那就是每次插入或者刪除需要平衡次數(shù)非常多摸屠。那么有什么什么折中的方案既能保證能正常二叉查找樹的性質(zhì)又能保證查找的效率。

紅黑樹

沒錯就是紅黑樹粱哼,不過為了理解紅黑樹我們需要做一些準備工作季二,首先我們要了解一下B-樹中最簡單的2-3樹。

這是一顆很神奇的樹揭措,包含2-結(jié)點(1個鍵兩個鏈接)和3-結(jié)點(2個鍵3個鏈接)胯舷。

我們只要記住幾點就是一個新的鍵插入2-結(jié)點直接變3-結(jié)點,



而當要插入到3-結(jié)點時绊含,那么變成4-結(jié)點桑嘶,4-結(jié)點中間的鍵值上升到父結(jié)點直到父節(jié)點是一個2-結(jié)點



或者到了根節(jié)點還不是2-結(jié)點那么根節(jié)點直接分解使高度加一

可能覺得2-3樹已經(jīng)完美解決我們的問題了,但現(xiàn)實往往不盡人意,為什么呢艺挪?不翩?

首先表示一顆2-3樹光表示2-結(jié)點和3-結(jié)點和它們之間的轉(zhuǎn)換就需要花費力氣兵扬,而且我們可能需要要跟結(jié)點之內(nèi)的鍵值進行比較麻裳,而它實際插入情況7種,代表著光判斷起碼寫7個if器钟,2-3樹解決我們的問題津坑,可是實際書寫顯得比較復雜

我們需要一種簡單的數(shù)據(jù)結(jié)構(gòu)來解決我們的問題對了就是紅黑樹,它其實就是2-3樹的變形.它其中有兩種鏈接一種是紅鏈接一種是黑鏈接(指向的結(jié)點顏色就是鏈接顏色)傲霸,用這兩種鏈接就能表示我們2-3樹疆瑰。如下圖。



當然紅黑樹有以下幾個特點:
1.紅鏈接均為左鏈接昙啄。
2.沒有任何一個結(jié)點和兩條紅鏈接相連穆役。
3.而且它是完美黑色平衡的(葉子結(jié)點到根結(jié)點黑色鏈接數(shù)量都是相同的)。
但是現(xiàn)實是殘酷的梳凛,我們在對紅黑樹做操作的時候是有可能出現(xiàn)紅色右鏈接或者連續(xù)兩條紅色鏈接耿币。我們這時候可以通過旋轉(zhuǎn)或者上移中鍵進行操作而對其進行操作



public class RBBST<Key extends Comparable<Key>, Value> {
    private Node root;
    private static final boolean RED = true;
    private static final boolean BLACK = false;
    private class Node {
        private Node left;
        private Node right;
        private boolean color;
        private Key key;
        private Value val;
        private int N;
        public Node(boolean color, Key key, Value val, int N) {
            this.color = color;
            this.key = key;
            this.val = val;
            this.N = N;
        }
    }
    public int size() {
        return size(root);
    }
    private int size(Node n) {
        if(n==null) {
            return 0;
        }
        return n.N;
    }
    private boolean isRed(Node n) {
        if(n==null) return false;
        return n.color == RED;
    }
    private Node rotateLeft(Node n) {
        Node x = n.right;
        n.right = x.left;
        x.left = n;
        x.color = n.color;
        n.color = RED;
        x.N = n.N;
        n.N = size(n.left)+size(n.right)+1;
        return x;
    }
    private Node rotateRight(Node n) {
        Node x = n.left;
        n.left = x.right;
        x.right = n;
        x.color = n.color;
        n.color = RED;
        x.N = n.N;
        n.N = size(n.left)+size(n.right)+1;//變了 變成x了
        return x;
    }
    private void change2Conn(Node n) {
        n.left.color = BLACK;
        n.right.color = BLACK;
        n.color = RED;
        //return n;還是原來的
    }
    public void put(Key key, Value val) {
        root = put(root, key, val);
        root.color = BLACK;
    }
    private Node put(Node n, Key key, Value val) {
        if(n==null) {
            return new Node(RED, key, val, 1);
        }
        int cmp = key.compareTo(n.key);
        if(cmp<0) n.left = put(n.left, key, val);
        else if(cmp>0) n.right = put(n.right, key, val);
        else n.val = val; //find key
        if(!isRed(n.left)&&isRed(n.right)) n = rotateLeft(n);
        if(isRed(n.left)&&isRed(n.left.left)) n = rotateRight(n);
        if(isRed(n.left)&&isRed(n.right)) change2Conn(n);
        n.N = size(n.left) + size(n.right) + 1;
        return n;
    }
}

你以為到這里就完了嗎,本著求知的態(tài)度我們繼續(xù)講一講HashMap這個Java里面無比精妙的類

HashMap 紅黑樹實現(xiàn)

就看兩段代碼理解了HashMap的紅黑樹實現(xiàn)也就大致理解了
左旋轉(zhuǎn)

static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root,
                                              TreeNode<K,V> p) {
            TreeNode<K,V> r, pp, rl;
            if (p != null && (r = p.right) != null) {
                if ((rl = p.right = r.left) != null)
                    rl.parent = p;
                if ((pp = r.parent = p.parent) == null)
                    (root = r).red = false;
                else if (pp.left == p)
                    pp.left = r;
                else
                    pp.right = r;
                r.left = p;
                p.parent = r;
            }
            return root;
        }

略微一看很難懂,這完全與我們自己的實現(xiàn)不同呀,傳兩個結(jié)點進來這是干什么韧拒?
跳過我們看具體插入的實現(xiàn)

  static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,
                                                    TreeNode<K,V> x) {
            x.red = true;
            for (TreeNode<K,V> xp, xpp, xppl, xppr;;) {
                if ((xp = x.parent) == null) {
                    x.red = false;
                    return x;
                }
                else if (!xp.red || (xpp = xp.parent) == null)
                    return root;
                if (xp == (xppl = xpp.left)) {
                    if ((xppr = xpp.right) != null && xppr.red) {
                        xppr.red = false;
                        xp.red = false;
                        xpp.red = true;
                        x = xpp;
                    }
                    else {
                        if (x == xp.right) {
                            root = rotateLeft(root, x = xp);
                            xpp = (xp = x.parent) == null ? null : xp.parent;
                        }
                        if (xp != null) {
                            xp.red = false;
                            if (xpp != null) {
                                xpp.red = true;
                                root = rotateRight(root, xpp);
                            }
                        }
                    }
                }
                else {
                    if (xppl != null && xppl.red) {
                        xppl.red = false;
                        xp.red = false;
                        xpp.red = true;
                        x = xpp;
                    }
                    else {
                        if (x == xp.left) {
                            root = rotateRight(root, x = xp);
                            xpp = (xp = x.parent) == null ? null : xp.parent;
                        }
                        if (xp != null) {
                            xp.red = false;
                            if (xpp != null) {
                                xpp.red = true;
                                root = rotateLeft(root, xpp);
                            }
                        }
                    }
                }
            }
        }

是否感覺略微的不適與暈眩淹接,但是你耐著性子看下去發(fā)現(xiàn)邏輯竟是如此的簡單。
root代表著在Node[]這個表中每一個位置上第一個結(jié)點,而x就是我們需要插入這個位置的結(jié)點當然如果兩者的key.equals(root.key)其實返回false不然就覆蓋了
當x的父親結(jié)點為空時叛溢,很明顯它就直接返回塑悼。

為了幫助更好理解這段代碼我們嘗試閱讀這么兩個函數(shù)

final void treeify(Node<K,V>[] tab) {
            TreeNode<K,V> root = null;
            for (TreeNode<K,V> x = this, next; x != null; x = next) {
                next = (TreeNode<K,V>)x.next;
                x.left = x.right = null;
                if (root == null) {
                    x.parent = null;
                    x.red = false;
                    root = x;
                }
                else {
                    K k = x.key;
                    int h = x.hash;
                    Class<?> kc = null;
                    for (TreeNode<K,V> p = root;;) {
                        int dir, ph;
                        K pk = p.key;
                        if ((ph = p.hash) > h)
                            dir = -1;
                        else if (ph < h)
                            dir = 1;
                        else if ((kc == null &&
                                  (kc = comparableClassFor(k)) == null) ||
                                 (dir = compareComparables(kc, k, pk)) == 0)
                            dir = tieBreakOrder(k, pk);
                        TreeNode<K,V> xp = p;
                        if ((p = (dir <= 0) ? p.left : p.right) == null) {
                            x.parent = xp;
                            if (dir <= 0)
                                xp.left = x;
                            else
                                xp.right = x;
                            root = balanceInsertion(root, x);
                            break;
                        }
                    }
                }
            }
            moveRootToFront(tab, root);
        }

上面這一段是當一個哈希桶元素數(shù)超過8且其中表的長度大于64的時候發(fā)生的對哈希桶進行樹型化。我們看到一開始是對根結(jié)點進行賦值的楷掉,但是中間插入了balanceInsertion()這個函數(shù)意味著root是為了滿足紅黑樹而不斷變化的厢蒜,而其中的moveRootToFront()是當本身在哈希桶第一個位置的元素不是root時進行把root賦值到哈希桶的第一個位置的值

大概意思就是本來是以鏈表的形式存的,然后不斷循環(huán)遍歷已經(jīng)通過treeifyBin()中的replacementTreeNode()從Node類型變?yōu)門reeNode類型的結(jié)點然后每次都循環(huán)都相當于對紅黑樹插入一個結(jié)點,當然插入后需要修復紅黑樹

看到這里我們可能明白了rotateleft()的作用了,跟普通紅黑樹的左旋差不多郭怪,就是把右子樹的左子樹掛在右子樹的父親結(jié)點的左子樹的地方支示,而右子樹又變成它父親結(jié)點的父親,成為它父親結(jié)點的父親結(jié)點的兒子鄙才。

可是有一點筆者邏輯思維有點混亂的是它的red屬性是怎么變換颂鸿,好像并不是按照正常方式左旋變換,因為左旋方法里面除了當是根結(jié)點的時候會讓其變?yōu)閒alse,而沒有任何地方作出改變了攒庵。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末嘴纺,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子浓冒,更是在濱河造成了極大的恐慌栽渴,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件稳懒,死亡現(xiàn)場離奇詭異闲擦,居然都是意外死亡,警方通過查閱死者的電腦和手機场梆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門墅冷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人或油,你說我怎么就攤上這事寞忿。” “怎么了顶岸?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵腔彰,是天一觀的道長。 經(jīng)常有香客問我辖佣,道長霹抛,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任卷谈,我火速辦了婚禮杯拐,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘雏搂。我一直安慰自己藕施,他們只是感情好,可當我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布凸郑。 她就那樣靜靜地躺著裳食,像睡著了一般。 火紅的嫁衣襯著肌膚如雪芙沥。 梳的紋絲不亂的頭發(fā)上诲祸,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天浊吏,我揣著相機與錄音,去河邊找鬼救氯。 笑死找田,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的着憨。 我是一名探鬼主播墩衙,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼甲抖!你這毒婦竟也來了漆改?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤准谚,失蹤者是張志新(化名)和其女友劉穎挫剑,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體柱衔,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡樊破,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了唆铐。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片哲戚。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖或链,靈堂內(nèi)的尸體忽然破棺而出惫恼,到底是詐尸還是另有隱情档押,我是刑警寧澤澳盐,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站令宿,受9級特大地震影響叼耙,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜粒没,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一筛婉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧癞松,春花似錦爽撒、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至枫甲,卻和暖如春源武,著一層夾襖步出監(jiān)牢的瞬間扼褪,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工粱栖, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留话浇,地道東北人。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓闹究,卻偏偏與公主長得像幔崖,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子渣淤,可洞房花燭夜當晚...
    茶點故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內(nèi)容