紅黑二叉查找樹(原理、實現(xiàn))

轉載請注明出處!http://www.lxweimin.com/p/d9d9f223f0ad

Github源碼地址

此篇需要二叉樹基本知識,若對二叉樹不了解,請移步一篇文章搞懂二叉樹!


Welcome back!

在了解了二叉樹的基本知識后,我們知道,二叉樹雖然可以在一般情況下控制比較次數(shù)在logN內(nèi)結束,但是一旦遇到較差的情況,導致樹的高度很高的時候,性能就不是很令人滿意了。另外,對于插入過程中保持二叉樹的完美動態(tài)平衡這一操作,代價又顯得太高了。所以,我們稍微放松點完美平衡的要求,將操作的性能保持在logN內(nèi)完成即可。

下面,在正式學習紅黑二叉樹之前,我們先學習另一種樹形結構“2-3查找樹(B-樹)”。這種樹形結構也是紅黑二叉樹形成的歷史由來,掌握它非常簡單,并且能更好的幫助我們理解紅黑二叉樹。

2-3查找樹

2-3查找樹只是高級數(shù)據(jù)結構B-樹中非常簡單的一種情況。之前二叉樹中了解到,操作的復雜度與樹的高度成正相關。那么在如何維持樹的高度和平衡性,我們這里需要一些靈活性。

  • 我們允許二叉樹中的一個結點保存多個鍵值
  • 我們將標準二叉樹中的結點(1個鍵值)稱之為2-結點。
  • 我們將含有2個鍵值和3個連接線(連接子結點)的稱之為3-結點。

一個2-3查找樹或是一個空樹,或是由下列結點組成:

  • 2-結點:含有1個鍵與2條連接線的結點,其與標準二叉樹的結點性質(zhì)一樣,x.left < x < x. right。
  • 3-結點:含有2個鍵與3條連接線的結點,其中左連接線都小于2個鍵,中連接線在2個鍵之間,右連接線都大于2個鍵。
2-3樹結構示意圖
  • 一個完美的2-3平衡樹,它的所有空鏈接到根節(jié)點的距離應該是相同的。

2-3樹 查找 get:

設查找的鍵為key,查找的節(jié)點為x。與二叉樹相似:

  • 若未命中,則返回null。
  • 若 key < x.key , 在x的左子樹中尋找。
  • 若 key > x.key , 在x的右子樹中尋找。
  • 若結點x是2-結點,則與二叉樹一樣。
  • 若結點是3-結點,則比較key與2個鍵的大小,看是直接命中還是在哪個連接線繼續(xù)尋找。


    2-3樹查找

2-3樹 插入 put:

  • 先進行一次未命中查找。如果命中,則替換value。如果沒有命中,則創(chuàng)建新結點掛在樹的底部。

向2-結點 h 中插入

直接掛在樹的底部會有些問題,破壞了樹的平衡性,我們想要保持插入也是平衡的,所以選擇結點上變化。那么分兩種情況:

  1. 如果h無子結點或h的子結點是2-結點,則與二叉樹一樣插入,2-結點變3-結點。


    結點為2結點插入
  2. 如果h的子結點是3-結點,則3-結點變成4-結點了。(見下)

向3-結點 h 中插入

  1. 如果向只含有一個3-結點的樹插入,則3-結點變4-結點。這個情況則需要稍微變形處理下,可以將4-結點轉化為2個2-結點。

規(guī)定:4-結點中有3個鍵,4個連接線。中間的鍵可以提到上面,成為左右兩個鍵的父結點,此時樹的高度+1。由于變換的是根節(jié)點,依然維持了完美平衡性

4-結點轉化為2個2-結點

理解樹的變化是非常重要的,這關乎2-3樹如何成長。

  1. 如果向父結點為2-結點的3-結點 h插入(接上),則3-結點變4-結點。變換后,提上來的中間鍵與父結點合并,注意,此時仍要保持父結點內(nèi)鍵的大小順序。


    父結點為3結點插入
  2. 如果向父結點為3-結點的3-結點 h插入,則子結點變?yōu)?-結點,變換后父結點也變?yōu)?-結點,則再次向上變換,直至沒有4-結點。


    父結點為3-結點
  3. 如果插入的路徑向上全是3-結點,則我們的根節(jié)點變?yōu)榕R時的4-結點,此時我們可以向情況1.中所描述的處理,分解4-結點,樹的高度+1。


    根節(jié)點為4-結點

請注意,2-3樹中任何4-節(jié)點的分解與轉化,均是局部操作,不會影響到樹的平衡性!
只要符合相應的形態(tài),變換即可進行。

2-3樹 性能:

2-3樹的高度必然在 (logN)/(log3) ~ logN 之間,故增刪查改的性能也在 O[ (logN)/(log3) ]~ O( logN ) 之間。

OK,2-3樹的介紹就到這里,代碼沒寫,因為我們的重點不是2-3樹,而是紅黑樹。現(xiàn)在我們離紅黑樹只差一句話的距離了。當然,在告別2-3樹之前,我們再看一眼2-3樹的完美身影 :) 。


一般的2-3樹,優(yōu)雅,美麗,請記住她!

(喝口水,休息一下)

紅黑二叉查找樹

首先,讓我們來去掉3-結點,就從2-3樹變成了紅黑樹。
本質(zhì)上,紅黑樹就是利用標準二叉樹中結點的額外信息表示2-3樹。所以,紅黑樹是一個二叉樹,或者說,紅黑樹是二叉樹的子類(不太準確的說法)。

去掉3-結點

上面說的結點的額外信息,其實就是指的(結點)鏈接的顏色。在標準二叉樹中,所有鏈接的顏色都是黑色,而在紅黑樹中,我們規(guī)定有紅色和黑色兩種鏈接。
注:不少教科書上都是用結點為紅/黑來定義紅黑樹,這里采用的是另一種定義規(guī)則,并不沖突。

  • 黑鏈接是2-3樹中的普通鏈接,也是標準二叉樹中的鏈接。
  • 紅鏈接是將兩個2-結點,變?yōu)橐粋€3-結點。

所以,我們只需要將3-結點還原成由紅鏈接連接的2個2-結點,即可去掉3-結點!而對于3-結點的子結點,則分配給兩個2-結點來完成。

紅鏈接是將兩個2-結點,變?yōu)橐粋€3-結點

等價定義

我們這里為了方便討論情況,不影響性質(zhì)的前提下,定義以下規(guī)則:

一個靜態(tài)的紅黑樹:(不需要再變換的、操作完成后的)

  • 所有紅鏈接均為左鏈接
  • 沒有任何一個結點與兩個紅鏈接相連
  • 該樹是完美黑色平衡的,即任意空結點到根結點的路徑上黑鏈接數(shù)量相同!(黑鏈數(shù)目既是樹的高度)

肯定有小朋友對上面三條規(guī)則不滿的,這邊我來解釋一下:

  1. 前提是變換好的紅黑樹。
  2. 紅鏈接為右鏈接可不可以,我說可以,但是為了統(tǒng)一和美觀,以及減少討論的情況和復雜度,我們統(tǒng)一規(guī)定左邊。
  3. 為啥不能兩個紅鏈接相連?兩個紅鏈接相連其實就產(chǎn)生了4-結點,這在2-3樹中是會被轉化的,而我們紅黑樹是由2-3樹演變而來,所以應該通過相應的轉化變換解決這個問題,后面會有介紹方法,且聽我細細說來。
  4. 因為完美平衡二叉樹是黑色平衡的,所以我們紅黑也是完美黑色平衡的。或者等會你就能明白,為什么我們不計入紅色為樹的高度了,看下面。
紅黑樹與2-3樹一一對應
  • 注意這里紅鏈接橫置后的連接順序,沒有改變

我們將紅黑樹中,所有的紅鏈接都橫過來畫(或者說,將2-3樹中所有3-結點內(nèi)部都加上紅鏈接),即顯示了為何紅黑樹也是2-3樹,并且紅黑樹的高度就是2-3樹的高度,也就是空結點到根節(jié)點路徑上普通黑鏈接的數(shù)量。

  • 紅黑樹既是二叉樹,也是2-3樹。
    所以我們在紅黑樹中可以使用2-3樹高效的插入算法,以及二叉樹中高效的查找算法。

結點構成及顏色表示

boolean RED = true
boolean BLACK = false

我們在二叉樹的基礎上,加入了結點的顏色來表示指向當前結點的鏈接顏色

C和G的顏色是紅的,E和J不是
    private class Node {
        private Node left;
        private Node right;
        private boolean color = BLACK;
        private Key key;
        private Value value;
        private int size;

        public Node(Key key, Value value, int size, boolean color) {
            this.key = key;
            this.value = value;
            this.size = size;
            this.color = color;
        }
    }
    
    private boolean isRed(Node h) {
        if (h == null) return false;
        return h.color;
    }

旋轉 rotate:

為了將我們所有的紅鏈接都能自由的左右旋轉,于是有以下兩個方法:假設結點為 h

向左旋轉 rotateLeft:

左旋可以將紅鏈從右邊移至左邊
這個方法多數(shù)用于刪除操作和調(diào)整樹的平衡,以滿足我們的等價定義。

左旋前

左旋后

當然從視覺上來看就是h 的右子結點不動,h 自己旋轉至左下方,同時,交換子結點和紅鏈。

  • 旋轉后需要重置父結點的鏈接。
  • 旋轉后需要調(diào)整結點的大小,因為結點的高度變化了。
    private Node rotateLeft(Node h) {
        if (h == null) throw new IllegalArgumentException();
        Node x = h.right;
        h.right = x.left;
        x.left = h;
        x.color = x.left.color;//x.color = h.color
        x.left.color = RED;
        x.size = h.size;
        h.size = 1 + size(h.left) + size(h.right);
        return x;
    }

向右旋轉 rotateRight:

右旋可以將紅鏈從左邊移至右邊
這個方法多數(shù)臨時用于刪除操作中,后面我們會介紹刪除操作。

右旋前

右旋后

操作與左旋完全相反,不再贅述。

    private Node rotateRight(Node h) {
        if (h == null) throw new IllegalArgumentException();
        Node x = h.left;
        h.left = x.right;
        x.right = h;
        x.color = x.right.color;//x.color = h.color
        x.right.color = RED;
        x.size = h.size;
        h.size = 1 + size(h.left) + size(h.right);
        return x;
    }

插入 put:

紅黑樹的插入是為數(shù)不多的比較復雜的實現(xiàn)之一(還有就是刪除),聯(lián)系2-3樹,這里我們先分情況討論:

  • 規(guī)定:插入的新結點的color都是RED
    這個只要想想2-3樹中插入就明白了。
向單個2-結點中插入新鍵:

如果一個紅黑樹只有一個2-結點,即根節(jié)點root,那么插入的時候會有三種情況:

  1. key == root,替換root 的值。
  2. key < root,則插入左子結點,此時不需要調(diào)整。
  3. key > root,則插入右子結點,此時為了滿足等價定義,rotateLeft一下就好了。
向樹底部的2-結點插入新鍵:

和上面三個情況差不多,只要保證我們的等價定義,以及二叉樹的基本定義(x.left < x < x.right),調(diào)整并更新父鏈接就好啦。

向一個雙鍵樹(3-結點)插入新鍵:

分以下三種情況:

1. 新插入的鍵最大:

插入右子結點x.right。則形成4-結點,此時需要進行變換。這里介紹一個flipColors方法,用來分解4-結點:

  • 一個鍵h 的左右子結點都是紅色,h 為黑色。
  • 將h 的左右子結點都變?yōu)楹谏琱 變?yōu)榧t色。
  • 此時樹的高度+1。
  • 調(diào)整后的h 可以根據(jù)其他情況進行繼續(xù)變換。


    變換前
變換后
    private Node flipColors(Node h) {
        if (h == null || h.left == null || h.right == null) throw new IllegalArgumentException();
        h.color = !h.color;
        h.left.color = !h.left.color;
        h.right.color = !h.right.color;
        return h;
    }
2. 新插入的鍵最小:

插入左子結點的子結點 x.left.left,則此時形成連續(xù)的左鏈接都是紅色(A 插入 B-C ,形成 A-B-C)。
這時候需要我們先對C進行右旋 rotateRight(C),然后就形成了上面1.的情況。

旋轉后flip

3. 新插入的鍵大小在兩個鍵之間:

插入左子結點的右結點 x.left.right,這種情況最為復雜。例如B插入A-C
此時需要先對A進行左旋 rotateLeft(A),然后就形成了上面2.的情況。

新插入的鍵大小在兩個鍵之間

  • 由此我們可以看出,插入總是在“ 情況3 -> 情況2 -> 情況1 ”之間轉化。
  • 請確保完全理解了上述中樹的變化,再繼續(xù)看下去。
根節(jié)點總是黑色

當我們進行插入后,根節(jié)點有時候會變?yōu)榧t色,此時當根節(jié)點由紅色轉為黑色時,樹的高度+1

代碼實現(xiàn):

插入的實現(xiàn),可以參考2-3樹,這里只是在插入完成后,重新調(diào)整樹的結構以達到完美平衡,滿足等價定義。

    public void put(Key key, Value value) {
        root = put(root, key, value);
        root.color = BLACK;//根節(jié)點總是黑色
    }

    private Node put(Node h, Key key, Value value) {
        if (key == null) throw new IllegalArgumentException();
        //創(chuàng)建新子結點,顏色為紅色
        if (h == null) return new Node(key, value, 1, RED);

        int comp = key.compareTo(h.key);
        if (comp > 0) h.right = put(h.right, key, value);
        else if (comp < 0) h.left = put(h.left, key, value);
        else h.value = value;

        //插入完成后重新調(diào)整樹以滿足等價定義
        if (isRed(h.right) && !isRed(h.left)) h = rotateLeft(h);//情況3 -> 情況2
        if (isRed(h.left) && isRed(h.left.left)) h = rotateRight(h);//情況2
        if (isRed(h.left) && isRed(h.right)) h = flipColors(h);//情況1

        //調(diào)整完成后需要重新計算樹的高度
        h.size = size(h.left) + size(h.right) + 1;
        return h;
    }

刪除 delete:

刪除任意一個鍵,是紅黑樹中最為復雜的算法。而在刪除任意結點的時候,我們先想想二叉樹中是如何進行刪除操作的。

當x不是樹的末結點,若直接刪除會造成空缺,樹不連續(xù),我們需要變換一下。
則當x不是樹的末結點且x有兩個子結點時:
刪除x后,需要尋找x的右子結點中最小的(或者x的左子結點中最大的)來代替x的位置,保證二叉樹的性質(zhì)“每個結點的鍵都大于任意左子節(jié)點而小于任意右子節(jié)點”

故我們可以這樣理解,最復雜的情況下,假設x不是樹的末結點且x有兩個子結點:
如果我們將x先和右結點最小的(或者左結點最大的)進行交換,然后就將x變?yōu)闃涞哪┙Y點,此時即可直接刪除。

所以,刪除的操作可以簡化為 刪除最小值刪除最大值 的操作。

刪除最小值 deleteMin:

由二叉樹性質(zhì)可知,最小鍵一定是在樹的最左邊。并且由等價定義可知,最小值一定是在樹的末端最左邊

當我們進行刪除末結點的時候,讓我們先回歸2-3樹,并且稍微允許臨時4-結點的存在。

  • 有時候為了讓父結點變?yōu)榧t色,需要臨時合并為4-結點。
  • 如果刪除的末結點是3-結點,則直接刪除即可。
  • 如果刪除的末結點是2-結點,直接刪除會破壞樹的完美平衡。所以此時我們需要進行變換。

分以下幾種情況:

  1. 如果此時要刪除的結點,它的父結點、兄弟結點(父結點的右結點)都是2-結點,則可以將這三個結點flipColors,還原為一個臨時的4-結點。這樣在我們刪除后,依然是3-結點,不會破壞完美平衡,高度-1。
    情況1.父結點、兄弟結點都是2-結點

如果它的父結點不是紅色,則想辦法變?yōu)榧t色

  1. 如果此時要刪除的結點,它的父結點是2-結點,而它的兄弟結點不是2-結點,則此時需要向兄弟結點借一個結點過來,形成3-結點。

    從兄弟結點中借一個結點,e可有可無

  2. 如果此時要刪除的結點,它的父結點不是2-結點,那么從父結點借一個結點過來,形成3-結點。

    兩種情況,分別是兄弟結點是否為2-結點

若兄弟結點不是2-結點,則從父結點借一個結點后,兄弟結點可以補給父結點一個最小的結點,保持樹的完美平衡性。
若兄弟結點是2-結點,則父結點中最小的結點向下合并,形成臨時4-結點。

待刪除完成后,需要自下而上重新整理樹的結構,將所有的臨時4-結點分解,從而滿足我們的等價定義。

代碼實現(xiàn):

沿著樹的最左路徑,一路向下的過程中,當遇到2-結點,實現(xiàn)一些變換moveRedLeft(),從而保證當前結點不是2-結點。
這里也就是想辦法變紅色,從而能夠刪除鍵而不破壞樹的完美平衡。

    private Node moveRedLeft(Node h) {
        //假設h為紅色,h.left 和h.left.left都是黑色(h.left和h.left.left都是2-結點)
        //將h.left 或h.left 的子結點之一變紅(想辦法變紅,變?yōu)?-結點)
        flipColors(h);//這個方法可以在拆分4-結點和組合4-結點之間變換。

        if (isRed(h.right.left)) {
        //兄弟結點為非2-結點,此時經(jīng)旋轉,將紅鍵從右往左傳遞。見下圖。
            h.right = rotateRight(h.right);
            h = rotateLeft(h);
        }
        return h;
    }
moveRedLeft 過程
    public void deleteMin() {
        if (isEmpty()) throw new NoSuchElementException();
        if (!isRed(root.left) && !isRed(root.right))
            root.color = RED;
        root = deleteMin(root);
        if (!isEmpty()) root.color = BLACK;
    }

    private Node deleteMin(Node h) {
        if (h.left == null) return null;
        if (!isRed(h.left) && !isRed(h.left.left))
            h = moveRedLeft(h);
        h.left = deleteMin(h.left);
        return balance(h);//自下而上重新整理樹的結構。
    }

一開始,如果根節(jié)點的兩個子鍵都沒有紅鍵,則需要我們臨時將根節(jié)點變紅,從而可以拆分出紅鍵。
而在刪除完成最后,如果樹還有結點,則要將根節(jié)點還原為黑色,以滿足根節(jié)點總是黑色
其中balance()方法與之前我們進行put后的操作類似,不再贅述。

    private Node balance(Node h) {
        if (!isRed(h.left) && isRed(h.right)) h = rotateLeft(h);
        if (isRed(h.left) && isRed(h.left.left)) h = rotateRight(h);
        if (isRed(h.left) && isRed(h.right)) flipColors(h);
        h.size = size(h.left) + size(h.right) + 1;
        return h;
    }

刪除最大值 deleteMax:

由于我們的紅鏈都是左鏈,所以這里與deleteMin稍有不同。

沿著樹的最右路徑,一路向下的過程中,當遇到2-結點,實現(xiàn)變換moveRedRight(),從而保證當前結點不是2-結點。

    private Node moveRedRight(Node h) {
        //假設h為紅色,h.right 和h.right.left都是黑色(h.right和h.right.left都是2-結點)
        //將h.right 或h.right 的子結點之一變紅(想辦法變紅,變?yōu)?-結點)
        flipColors(h);
        if (!isRed(h.left.left))//兩個子結點均為2-結點
            h = rotateRight(h);
        return h;
    }
兩個子結點都是2-結點的刪除示意

如圖示不難理解,由于我們刪除的是最大值,所以鍵一定在右子結點中,故要將紅鍵從左往右傳遞。其與操作與deleteMin差不多,就不贅述了。但是要記住,我們這些局部的旋轉和移動都不會改變樹的組成(見2-3樹性質(zhì))。

    public void deleteMax() {
        if (isEmpty()) throw new NoSuchElementException();
        if (!isRed(root.left) && !isRed(root.right))
            root.color = RED;
        root = deleteMax(root);
        if (!isEmpty()) 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.right是個2-結點
            h = moveRedRight(h);//此時將紅鍵從左向右傳遞
        h.right = deleteMax(h.right);
        return balance(h);
    }

刪除任意結點的實現(xiàn):

刪除任意結點就是轉為為刪除最小值和刪除最大值的操作。

    public void delete(Key key) {
        if (key == null) throw new IllegalArgumentException();
        if (isEmpty()) throw new NoSuchElementException();
        if (!isRed(root.left) && !isRed(root.right))
            root.color = RED;
        root = delete(root, key);
        if (!isEmpty()) root.color = BLACK;
    }

上面這段代碼就不解釋了,和之前deleteMin的原因一樣。我們主要看下面的具體實現(xiàn)。

    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.value = 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);
    }
  • 如果尋找的鍵在左邊,則消除左邊路徑上的2-結點,參考deleteMin
  • 如果尋找的鍵在右邊,則消除右邊路徑上的2-結點,參考deleteMax
  • 我們在這里主要采用的是尋找右子鍵中最小值來交換自己的位置,此時待刪除結點就從樹的中間部分被交換到了樹的末端,從而刪除右子鍵中的最小值,簡化為刪除最小值的問題。
  • 最后也要記得自下而上整理整個樹的結構,滿足我們的等價定義。


    刪除B的簡易示意圖

性能:

  • 無論我們?nèi)绾尾迦腈I值,紅黑樹都是幾乎完美平衡的。

這個可以通過我們2-3樹的性質(zhì)得到。

  • 大小為N的紅黑樹的高度不會超過2logN。

所以其操作復雜度始終維持在 ~O(logN)。一般來說,想構建2logN高度的紅黑樹比較困難,需要最左邊一條路徑均是3-結點,而其他的子結點都是2-結點才可以。我們在一般數(shù)據(jù)里很少能遇到。

  • 大小為N的紅黑樹,根節(jié)點到任意子結點的平均路徑長度為 ~1.00logN

這個性質(zhì)很容易得到。而紅黑樹相對于二叉樹而言,我們提高了40%以上的性能,能夠使任何操作都在 ~O(logN) 內(nèi)完成。

結束語:

復雜的紅黑樹終于了解完了,可以看出,理解2-3樹對于掌握紅黑樹的原理至關重要。在樹的生長變換中,局部變換并不會改變整體的結構這一點非常重要,也是紅黑樹的靈魂。

在下一篇文章,將會帶來Java中另一個最常用的數(shù)據(jù)結構——散列表。

謝謝觀看。


參考文獻:《算法導論》 《Algorithms, 4th Edition》

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,990評論 2 374

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

  • 數(shù)據(jù)結構與算法--從平衡二叉樹(AVL)到紅黑樹 上節(jié)學習了二叉查找樹。算法的性能取決于樹的形狀,而樹的形狀取決于...
    sunhaiyu閱讀 7,674評論 4 32
  • 一. 算法之變,結構為宗 計算機在很多情況下被應用于檢索數(shù)據(jù),比如航空和鐵路運輸業(yè)的航班信息和列車時刻表的查詢,都...
    Leesper閱讀 6,971評論 13 42
  • B樹的定義 一棵m階的B樹滿足下列條件: 樹中每個結點至多有m個孩子。 除根結點和葉子結點外,其它每個結點至少有m...
    文檔隨手記閱讀 13,282評論 0 25
  • 姓名:陳權 公司:青檸養(yǎng)車 【知~學習】 《原則》音頻打卡第25天 《輕課口語》打卡第477天 《財富自由》音頻打...
    水青檸閱讀 139評論 0 0
  • 在企業(yè)中,公司差不多解決生存的問題后,大多數(shù)老板就開始考慮人才培養(yǎng)的事了,開始要求人資部門做人才培養(yǎng)計劃。...
    話西東閱讀 317評論 0 0