【轉(zhuǎn)載】深入理解紅黑樹(shù)和 JDK TreeMap 和 TreeSet 源碼分析

本文主要包括以下內(nèi)容:

  1. 什么是2-3樹(shù)
  2. 2-3樹(shù)的插入操作
  3. 紅黑樹(shù)與2-3樹(shù)的等價(jià)關(guān)系
  4. 《算法4》和《算法導(dǎo)論》上關(guān)于紅黑樹(shù)的差異
  5. 紅黑樹(shù)的5條基本性質(zhì)的分析
  6. 紅黑樹(shù)與2-3-4樹(shù)的等價(jià)關(guān)系
  7. 紅黑樹(shù)的插入抵拘、刪除操作
  8. JDK TreeMap酒朵、TreeSet分析

今天我們來(lái)介紹下非常重要的數(shù)據(jù)結(jié)構(gòu):紅黑樹(shù)。

很多文章或書(shū)籍在介紹紅黑樹(shù)的時(shí)候直接上來(lái)就是紅黑樹(shù)的5個(gè)基本性質(zhì)男旗、插入萧朝、刪除操作等秘症。本文不是采用這樣的介紹方式垒棋,在介紹紅黑樹(shù)之前爬范,我們要了解紅黑樹(shù)是怎么發(fā)展出來(lái)的,進(jìn)而就能知道為什么會(huì)有紅黑樹(shù)的5條基本性質(zhì)椅邓。

這樣的介紹方式也是《算法4》的介紹方式柠逞。這也不奇怪,《算法4》的作者 Robert Sedgewick 就是紅黑樹(shù)的作者之一景馁。在介紹紅黑樹(shù)之前板壮,我們先來(lái)看下2-3樹(shù)

什么是2-3樹(shù)

在介紹紅黑樹(shù)之前為什么要先介紹 2-3樹(shù) 呢?因?yàn)榧t黑樹(shù)是 完美平衡的2-3樹(shù) 的一種實(shí)現(xiàn)合住。所以绰精,理解2-3樹(shù)對(duì)掌握紅黑樹(shù)是至關(guān)重要的。

2-3樹(shù) 的一個(gè)Node可能有多個(gè)子節(jié)點(diǎn)(可能大于2個(gè))透葛,而且一個(gè)Node可以包含2個(gè)鍵(元素)

可以把 紅黑樹(shù)(紅黑二叉查找樹(shù)) 當(dāng)作 2-3樹(shù) 的一種二叉結(jié)構(gòu)的實(shí)現(xiàn)笨使。

在前面介紹的二叉樹(shù)中,一個(gè)Node保存一個(gè)值僚害,在2-3樹(shù)中把這樣的節(jié)點(diǎn)稱之為 2- 節(jié)點(diǎn)

如果一個(gè)節(jié)點(diǎn)包含了兩個(gè)(可以當(dāng)作兩個(gè)節(jié)點(diǎn)的融合)硫椰,在2-3樹(shù)中把這樣的節(jié)點(diǎn)稱之為 3- 節(jié)點(diǎn)。 完美平衡的2-3樹(shù)所有空鏈接到根節(jié)點(diǎn)的距離都應(yīng)該是相同的

下面看下《算法4》對(duì) 2-3-節(jié)點(diǎn)的定義:

  • 2- 節(jié)點(diǎn)萨蚕,含有一個(gè)鍵(及其對(duì)應(yīng)的值)和兩條鏈接靶草。該節(jié)點(diǎn)的左鏈接小于該節(jié)點(diǎn)的鍵;該節(jié)點(diǎn)的右鏈接大于該節(jié)點(diǎn)的鍵
  • 3- 節(jié)點(diǎn)岳遥,含有兩個(gè)鍵(及其對(duì)應(yīng)的值)和三條鏈接奕翔。左鏈接小于該節(jié)點(diǎn)的左鍵;中鏈接在左鍵和右鍵之間浩蓉;右鏈接大于該節(jié)點(diǎn)右鍵

如下面一棵 完美平衡的2-3樹(shù)

完美平衡的2-3樹(shù)

2-3樹(shù) 是一棵多叉搜索樹(shù)派继,所以數(shù)據(jù)的插入類似二分搜索樹(shù)

2-3樹(shù)的插入操作

紅黑樹(shù)是對(duì) 完美平衡的2-3樹(shù) 的一種實(shí)現(xiàn)帮坚,所以我們主要介紹完美平衡的2-3樹(shù)的插入過(guò)程

完美平衡的2-3樹(shù)插入分為以下幾種情況(為了方便畫(huà)圖默認(rèn)把空鏈接去掉):

向 2- 結(jié)點(diǎn)中插入新鍵

向 2- 結(jié)點(diǎn)中插入新鍵

向一棵只含有一個(gè)3-結(jié)點(diǎn)的樹(shù)中插入新鍵

因?yàn)?-3樹(shù)中節(jié)點(diǎn)只能是2-節(jié)點(diǎn)或者3-節(jié)點(diǎn)

往3-點(diǎn)中再插入一個(gè)鍵就成了4-節(jié)點(diǎn),需要對(duì)其進(jìn)行分解互艾,如下所示:

向一棵只含有一個(gè)3-結(jié)點(diǎn)的樹(shù)中插入新鍵

向一個(gè)父結(jié)點(diǎn)為 2- 結(jié)點(diǎn)的 3- 結(jié)點(diǎn)插入新鍵

往3-點(diǎn)中再插入一個(gè)鍵就成了4-節(jié)點(diǎn)试和,需要對(duì)其進(jìn)行分解,對(duì)中間的鍵向上融合

由于父結(jié)點(diǎn)是一個(gè) 2- 結(jié)點(diǎn) 纫普,融合后變成了 3- 結(jié)點(diǎn)阅悍,然后把 4- 結(jié)點(diǎn)的左鍵變成該 3- 節(jié)點(diǎn)的中間子結(jié)點(diǎn)

向一個(gè)父結(jié)點(diǎn)為2-結(jié)點(diǎn)的3-結(jié)點(diǎn)插入新鍵

向一個(gè)父結(jié)點(diǎn)為3- 結(jié)點(diǎn)的 3- 結(jié)點(diǎn)中插入新鍵

在這種情況下,向3- 結(jié)點(diǎn)插入新鍵形成暫時(shí)的4- 結(jié)點(diǎn)昨稼,向上分解节视,父節(jié)點(diǎn)又形成一個(gè)4- 結(jié)點(diǎn),然后繼續(xù)上分解

向一個(gè)父結(jié)點(diǎn)為3-結(jié)點(diǎn)的3-結(jié)點(diǎn)中插入新鍵

一個(gè) 4- 結(jié)點(diǎn)分解為一棵2-3樹(shù)6種情況

一個(gè)4- 結(jié)點(diǎn)分解為一棵2-3樹(shù)6種情況

紅黑樹(shù)(RedBlackTree)

完美平衡的2-3樹(shù)和紅黑樹(shù)的對(duì)應(yīng)關(guān)系

上面介紹完了2-3樹(shù)假栓,下面來(lái)看下紅黑樹(shù)是怎么來(lái)實(shí)現(xiàn)一棵完美平衡的2-3樹(shù)的

紅黑樹(shù)的背后的基本思想就是用標(biāo)準(zhǔn)的二分搜索樹(shù)和一些額外的信息來(lái)表示2-3樹(shù)的

這額外的信息指的是什么呢寻行?因?yàn)?-3樹(shù)不是二叉樹(shù)(最多有3叉),所以需要把 3- 結(jié)點(diǎn) 替換成 2- 結(jié)點(diǎn)

額外的信息就是指替換3-結(jié)點(diǎn)的方式

將2-3樹(shù)的鏈接定義為兩種類型:黑鏈接匾荆、紅鏈接

黑鏈接 是2-3樹(shù)中普通的鏈接拌蜘,可以把2-3樹(shù)中的 2- 結(jié)點(diǎn) 與它的子結(jié)點(diǎn)之間的鏈當(dāng)作黑鏈接

紅鏈接 2-3樹(shù)中 3- 結(jié)點(diǎn)分解成兩個(gè) 2- 結(jié)點(diǎn),這兩個(gè) 2- 結(jié)點(diǎn)之間的鏈接就是紅鏈接

那么如何將2-3樹(shù)和紅黑樹(shù)等價(jià)起來(lái)牙丽,我們規(guī)定:紅鏈接均為左鏈接

根據(jù)上面對(duì)完美平衡的2-3樹(shù)紅鏈接的介紹可以得出結(jié)論:沒(méi)有一個(gè)結(jié)點(diǎn)同時(shí)和兩個(gè)紅鏈接相連

根據(jù)上面對(duì)完美平衡的2-3樹(shù)黑鏈接的介紹可以得出結(jié)論:完美平衡的2-3樹(shù)是保持完美黑色平衡的简卧,任意空鏈接到根結(jié)點(diǎn)的路徑上的黑鏈接數(shù)量相同

據(jù)此,我們可以得出3條性質(zhì):

  1. 紅鏈接均為左鏈接
  2. 沒(méi)有一個(gè)結(jié)點(diǎn)同時(shí)和兩個(gè)紅鏈接相連
  3. 完美平衡的2-3樹(shù)是保持完美黑色平衡的烤芦,任意空鏈接到根結(jié)點(diǎn)的路徑上的黑鏈接數(shù)量相同

在紅黑樹(shù)中举娩,沒(méi)有一個(gè)對(duì)象來(lái)表示紅鏈接和黑鏈接,通過(guò)在結(jié)點(diǎn)上加上一個(gè)屬性(color)來(lái)標(biāo)識(shí)紅鏈接還是黑鏈接构罗,color值為red表示結(jié)點(diǎn)是紅結(jié)點(diǎn)铜涉,color值為black表示結(jié)點(diǎn)是黑結(jié)點(diǎn)。

黑結(jié)點(diǎn) 2-3樹(shù)中普通的 2-結(jié)點(diǎn) 的顏色
紅結(jié)點(diǎn) 2-3樹(shù)中 3- 結(jié)點(diǎn) 分解出兩個(gè) 2-結(jié)點(diǎn) 的最小 2-結(jié)點(diǎn)

下面是2-3樹(shù)和紅黑樹(shù)的一一對(duì)應(yīng)關(guān)系圖:

在這里插入圖片描述

紅黑樹(shù)的5個(gè)基本性質(zhì)的分析

介紹完了2-3樹(shù)和紅黑樹(shù)的對(duì)應(yīng)關(guān)系后遂唧,我們?cè)賮?lái)看下紅黑樹(shù)的5個(gè)基本性質(zhì):

  1. 每個(gè)結(jié)點(diǎn)要么是紅色芙代,要么是黑色
  2. 根結(jié)點(diǎn)是黑色
  3. 每個(gè)葉子結(jié)點(diǎn)(最后的空節(jié)點(diǎn))是黑色
  4. 如果一個(gè)結(jié)點(diǎn)是紅色的,那么他的孩子結(jié)點(diǎn)都是黑色的
  5. 從任意一個(gè)結(jié)點(diǎn)到葉子結(jié)點(diǎn)蠢箩,經(jīng)過(guò)的黑色結(jié)點(diǎn)是一樣的

2-3樹(shù)和紅黑樹(shù)的對(duì)應(yīng)關(guān)系后我們也就知道了紅黑樹(shù)的5個(gè)基本性質(zhì)是怎么來(lái)的了

紅黑樹(shù)的第一條性質(zhì):每個(gè)節(jié)點(diǎn)要么是紅色链蕊,要么是黑色

因?yàn)槲覀冇媒Y(jié)點(diǎn)上的屬性來(lái)表示紅鏈還是黑鏈,所以紅黑樹(shù)的結(jié)點(diǎn)要么是紅色谬泌,要么是黑色是很自然的事情

紅黑樹(shù)的第二條性質(zhì):根結(jié)點(diǎn)是黑色

紅色節(jié)點(diǎn)的情況是 3- 結(jié)點(diǎn)分解出兩個(gè) 2- 結(jié)點(diǎn)的最小節(jié)點(diǎn)是紅色,根節(jié)點(diǎn)沒(méi)有父節(jié)點(diǎn)所以只能是黑色

紅黑樹(shù)的第三條性質(zhì):每個(gè)葉子結(jié)點(diǎn)(最后的空節(jié)點(diǎn))是黑色

葉子節(jié)點(diǎn)也就是2-3樹(shù)中的空鏈逻谦,如果空鏈?zhǔn)羌t色說(shuō)明下面還是有子結(jié)點(diǎn)的掌实,但是空鏈?zhǔn)菦](méi)有子結(jié)點(diǎn)的;另一方面如果
空鏈?zhǔn)羌t色邦马,空鏈指向的父結(jié)點(diǎn)結(jié)點(diǎn)如果也是紅色就會(huì)出現(xiàn)兩個(gè)連續(xù)的紅色鏈接贱鼻,就和上面介紹的 “沒(méi)有一個(gè)結(jié)點(diǎn)同時(shí)和兩個(gè)紅鏈接相連” 相違背

紅黑樹(shù)的第四條性質(zhì):如果一個(gè)結(jié)點(diǎn)是紅色的宴卖,那么他的孩子結(jié)點(diǎn)都是黑色的

上面介紹的‘沒(méi)有一個(gè)結(jié)點(diǎn)同時(shí)和兩個(gè)紅鏈接相連’,所以一個(gè)結(jié)點(diǎn)是紅色邻悬,那么他的孩子結(jié)點(diǎn)都是黑色

紅黑樹(shù)的第五條性質(zhì):從任意一個(gè)結(jié)點(diǎn)到葉子結(jié)點(diǎn)症昏,經(jīng)過(guò)的黑色結(jié)點(diǎn)是一樣的

在介紹完美平衡的2-3樹(shù)和黑鏈接我們得出的結(jié)論:‘完美平衡的2-3樹(shù)是保持完美黑色平衡的,任意空鏈接到根結(jié)點(diǎn)的路徑上的黑鏈接數(shù)量相同’父丰, 所以從任意一個(gè)結(jié)點(diǎn)到葉子結(jié)點(diǎn)肝谭,經(jīng)過(guò)的黑色結(jié)點(diǎn)數(shù)是一樣的

紅黑樹(shù)實(shí)現(xiàn)2-3樹(shù)過(guò)程中的結(jié)點(diǎn)旋轉(zhuǎn)和顏色翻轉(zhuǎn)

顏色翻轉(zhuǎn)

為什么要顏色翻轉(zhuǎn)(flipColor)?在插入的過(guò)程中可能出現(xiàn)如下情況:兩個(gè)左右子結(jié)點(diǎn)都是紅色

顏色翻轉(zhuǎn)

根據(jù)我們上面的描述蛾扇,紅鏈只允許是左鏈(也就是左子結(jié)點(diǎn)是紅色)

所以需要進(jìn)行顏色轉(zhuǎn)換:把該結(jié)點(diǎn)的左右子結(jié)點(diǎn)設(shè)置為黑色攘烛,自己設(shè)置為黑色

private void flipColor(Node<K, V> node) {
    node.color = RED;
    node.left.color = BLACK;
    node.right.color = BLACK;
}

左旋轉(zhuǎn)

左旋情況大致有兩種:

結(jié)點(diǎn)是右子結(jié)點(diǎn)且是紅色

左旋轉(zhuǎn)1

顏色翻轉(zhuǎn)后,結(jié)點(diǎn)變成紅色且它是父結(jié)點(diǎn)的右子節(jié)點(diǎn)

顏色翻轉(zhuǎn)
private Node<K, V> rotateLeft(Node<K, V> node) {
    Node<K, V> x = node.right;
    node.right = x.left;

    x.left = node;
    x.color = node.color;

    node.color = RED;
    return x;
}

右旋轉(zhuǎn)

需要右旋的情況:連續(xù)出現(xiàn)兩個(gè)左紅色鏈接

右旋
private Node<K, V> rotateRight(Node<K, V> node) {
    Node<K, V> x = node.left;
    node.left = x.right;
    x.right = node;

    x.color = node.color;
    node.color = RED;

    return x;
}

紅黑樹(shù)實(shí)現(xiàn)2-3樹(shù)插入操作

通過(guò)我們上面對(duì)紅黑樹(shù)和2-3樹(shù)的介紹镀首,紅黑樹(shù)實(shí)現(xiàn)2-3樹(shù)插入操作就很簡(jiǎn)單了

只要滿足不出現(xiàn) 兩個(gè)連續(xù)左紅色鏈接坟漱、右紅色鏈接左右都是紅色鏈接 的情況就可以了

所以僅僅需要處理三種情況即可:

  1. 如果出現(xiàn)右側(cè)紅色鏈接更哄,需要左旋
  2. 如果出現(xiàn)兩個(gè)連續(xù)的左紅色鏈接芋齿,需要右旋
  3. 如果結(jié)點(diǎn)的左右子鏈接都是紅色,需要顏色翻轉(zhuǎn)
private Node<K, V> _add(Node<K, V> node, K key, V value) {
    //向葉子結(jié)點(diǎn)插入新結(jié)點(diǎn)
    if (node == null) {
        size++;
        return new Node<>(key, value);
    }

    //二分搜索的過(guò)程
    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;

    //1,如果出現(xiàn)右側(cè)紅色鏈接成翩,左旋
    if (isRed(node.right) && !isRed(node.left)) {
        node = rotateLeft(node);
    }

    //2,如果出現(xiàn)兩個(gè)連續(xù)的左紅色鏈接沟突,右旋
    if (isRed(node.left) && isRed(node.left.left)) {
        node = rotateRight(node);
    }

    //3,如果結(jié)點(diǎn)的左右子鏈接都是紅色,顏色翻轉(zhuǎn)
    if (isRed(node.left) && isRed(node.right)) {
        flipColor(node);
    }
}

public void add(K key, V value) {
    root = _add(root, key, value);
    root.color = BLACK;
}

這樣下來(lái)紅黑樹(shù)依然保持著它的五個(gè)基本性質(zhì)捕传,下面我們來(lái)對(duì)比下JDK中的TreeMap的插入操作

先按照上面的紅黑樹(shù)插入邏輯插入三個(gè)元素 [14, 5, 20]惠拭,流程如下:

流程

使用Java TreeMap來(lái)插入上面三個(gè)元素,流程如下:

流程

通過(guò)對(duì)比我們發(fā)現(xiàn)兩者的插入后的結(jié)果不一樣庸论,而且Java TreeMap是允許左右子結(jié)點(diǎn)都是紅色結(jié)點(diǎn)职辅!

這就和我們一直在說(shuō)的用完美平衡的2-3樹(shù)作為紅黑樹(shù)實(shí)現(xiàn)的基礎(chǔ)結(jié)構(gòu)相違背了,我們一直在強(qiáng)調(diào)不允許右節(jié)點(diǎn)是紅色聂示,也不允許兩個(gè)連續(xù)的紅色左節(jié)點(diǎn)域携,不允許左右結(jié)點(diǎn)同時(shí)是紅色

這也是《算法4》在講到紅黑樹(shù)時(shí)遵循的。但是JDK TreeMap(紅黑樹(shù))是允許右結(jié)點(diǎn)是紅色鱼喉,也允許左右結(jié)點(diǎn)同時(shí)是紅色秀鞭,Java TreeMap的紅黑樹(shù)實(shí)現(xiàn)從它的代碼注釋(From CLR)說(shuō)明它的實(shí)現(xiàn)來(lái)自《算法導(dǎo)論》

說(shuō)明《算法4》和《算法導(dǎo)論》中的所介紹的紅黑樹(shù)產(chǎn)生了一些“出入”,給我們理解紅黑樹(shù)增加了一些困惑和難度

《算法4》在介紹紅黑樹(shù)之前先給我們?cè)敿?xì)介紹了2-3樹(shù)扛禽,然后接著講到完美平衡的2-3樹(shù)和紅黑樹(shù)的對(duì)應(yīng)關(guān)系(紅黑樹(shù)就等于完美平衡的2-3樹(shù))锋边,讓我們知道紅黑樹(shù)是怎么來(lái)的,根據(jù)這些介紹你自己是可以解釋紅黑樹(shù)的的5個(gè)基本性質(zhì)為什么是這樣的编曼。

而在《算法導(dǎo)論》中介紹紅黑樹(shù)的時(shí)候沒(méi)有提及2-3樹(shù)豆巨,直接就是紅黑樹(shù)的5個(gè)基本性質(zhì),以及紅黑樹(shù)的插入掐场、刪除操作往扔,感覺(jué)對(duì)初學(xué)者是不太合適的贩猎,因?yàn)槟悴恢罏槭裁词沁@樣的,只是知道有這個(gè)五個(gè)性質(zhì)萍膛,也許這就是為什么它叫導(dǎo)論的原因吧

而且在《算法4》中作者最后好像也沒(méi)有明確的給出紅黑樹(shù)的五個(gè)基本性質(zhì)吭服,在《算法導(dǎo)論》中在紅黑樹(shù)章節(jié)一開(kāi)始就貼出了5條性質(zhì),感覺(jué)像是一種遞進(jìn)和升華

這兩本書(shū)除了對(duì)紅黑樹(shù)講解的方式存在差異外蝗罗,我們還發(fā)現(xiàn)《算法4》和《算法導(dǎo)論》在紅黑樹(shù)的實(shí)現(xiàn)上也是有差異的艇棕,就如我們上面插入三個(gè)元素 [14, 5, 20] 產(chǎn)生不同的結(jié)果

在解釋這些差異之前,我們?cè)賮?lái)看些2-3-4樹(shù)绿饵,上面提到完美平衡的2-3樹(shù)和紅黑樹(shù)等價(jià)欠肾,更準(zhǔn)確的說(shuō)是2-3-4樹(shù)和紅黑樹(shù)等價(jià)

2-3-4樹(shù)

2-3-4樹(shù)2-3樹(shù) 非常相像。2-3樹(shù)允許存在 2- 結(jié)點(diǎn)3- 結(jié)點(diǎn)拟赊,類似的2-3-4樹(shù)允許存在 2- 結(jié)點(diǎn)刺桃、3- 結(jié)點(diǎn)4- 結(jié)點(diǎn)

2-3-4樹(shù)

向2-結(jié)點(diǎn)、3-結(jié)點(diǎn)插入元素

2-結(jié)點(diǎn)插入元素吸祟,這個(gè)和上面介紹的2-3樹(shù)是一樣的瑟慈,在這里就不敘述了

3-結(jié)點(diǎn)插入元素,形成一個(gè)4-結(jié)點(diǎn)屋匕,因?yàn)?-3-4樹(shù)允許4-結(jié)點(diǎn)的存在葛碧,所以不需要向上分解

向4-結(jié)點(diǎn)插入元素

向4-結(jié)點(diǎn)插入元素,需要分解4-結(jié)點(diǎn)过吻, 因?yàn)?-3-4樹(shù)最多只允許存在4-結(jié)點(diǎn)进泼,如:

在這里插入圖片描述

如果待插入的4-結(jié)點(diǎn),它的父結(jié)點(diǎn)也是一個(gè)4-結(jié)點(diǎn)呢纤虽?如下圖的2-3-4樹(shù)插入結(jié)點(diǎn)K:

在這里插入圖片描述

主要有兩個(gè)方案:

  1. Bayer于1972年提出的方案:使用相同的辦法去分解父結(jié)點(diǎn)的4-結(jié)點(diǎn)乳绕,直到不需要分解為止,方向是自底向上
  2. GuibasSedgewick于1978年提出的方案:自上而下的方式逼纸,也就是在二分搜索的過(guò)程洋措,一旦遇到4-結(jié)點(diǎn)就分解它,這樣在最終插入的時(shí)候永遠(yuǎn)不會(huì)有父結(jié)點(diǎn)是4-結(jié)點(diǎn)的情況

Bayer全名叫做Rudolf Bayer(魯?shù)婪颉ぐ轄枺?/strong>杰刽,他在1972年發(fā)明的 對(duì)稱二叉B樹(shù)(symmetric binary B-tree) 就是 紅黑樹(shù)(red black tree) 的前身菠发。
紅黑樹(shù) 這個(gè)名字是由 Leo J. GuibasRobert Sedgewick 于1978年的一篇論文中提出來(lái)的,
對(duì)該論文感興趣的可以查看這個(gè)鏈接:http://professor.ufabc.edu.br/~jesus.mena/courses/mc3305-2q-2015/AED2-13-redblack-paper.pdf

下面的圖就是 自上而下 方案的流程圖

自上而下

2-3-4樹(shù)和紅黑樹(shù)的等價(jià)關(guān)系

在介紹2-3樹(shù)的時(shí)候我們也講解了2-3樹(shù)和紅黑樹(shù)的等價(jià)關(guān)系贺嫂,由于2-3樹(shù)和2-3-4樹(shù)非常類似滓鸠,所以2-3-4樹(shù)和紅黑樹(shù)的等價(jià)關(guān)系也是類似的。不同的是2-3-4的 4-結(jié)點(diǎn) 分解后的結(jié)點(diǎn)顏色變成如下形式:

4-結(jié)點(diǎn)分解圖

所以可以得出下面一棵 2-3-4 樹(shù)和紅黑樹(shù)的等價(jià)關(guān)系圖:

2-3-4樹(shù)和紅黑樹(shù)的等價(jià)

上面在介紹紅黑樹(shù)實(shí)現(xiàn)2-3樹(shù)的時(shí)候講解了它的插入操作:

private Node<K, V> _add(Node<K, V> node, K key, V value) {
    //向葉子結(jié)點(diǎn)插入新結(jié)點(diǎn)
    if (node == null) {
        size++;
        return new Node<>(key, value);
    }

    //二分搜索的過(guò)程
    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;

    //1,如果出現(xiàn)右側(cè)紅色鏈接涝婉,左旋
    if (isRed(node.right) && !isRed(node.left)) {
        node = rotateLeft(node);
    }

    //2,如果出現(xiàn)兩個(gè)連續(xù)的左紅色鏈接哥力,右旋
    if (isRed(node.left) && isRed(node.left.left)) {
        node = rotateRight(node);
    }

    //3,如果結(jié)點(diǎn)的左右子鏈接都是紅色,顏色翻轉(zhuǎn)
    if (isRed(node.left) && isRed(node.right)) {
        flipColor(node);
    }
}

我們可以很輕松的把它改成 2-3-4 的插入邏輯(只需要把顏色翻轉(zhuǎn)的邏輯提到二分搜索的前面即可):

private Node<K, V> _add(Node<K, V> node, K key, V value) {
    //向葉子結(jié)點(diǎn)插入新結(jié)點(diǎn)
    if (node == null) {
        size++;
        return new Node<>(key, value);
    }

    //split 4-nodes on the way down
    if (isRed(node.left) && isRed(node.right)) {
        flipColor(node);
    }

    //二分搜索的過(guò)程
    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;

    //fix right-leaning reds on the way up
    if (isRed(node.right) && !isRed(node.left)) {
        node = rotateLeft(node);
    }

    //fix two reds in a row on the way up
    if (isRed(node.left) && isRed(node.left.left)) {
        node = rotateRight(node);
    }

}

//使用2-3-4樹(shù)插入數(shù)據(jù) [E,C,G,B,D,F,J,A]

RB2_3_4Tree<Character, Character> rbTree = new RB2_3_4Tree<>();
rbTree.add('E', 'E');
rbTree.add('C', 'C');
rbTree.add('G', 'G');
rbTree.add('B', 'B');
rbTree.add('D', 'D');
rbTree.add('F', 'F');
rbTree.add('J', 'J');
rbTree.add('A', 'A');
rbTree.levelorder(rbTree.root);

//使用2-3樹(shù)插入數(shù)據(jù) [E,C,G,B,D,F,J,A]

RBTree<Character, Character> rbTree = new RBTree<>();
rbTree.add('E', 'E');
rbTree.add('C', 'C');
rbTree.add('G', 'G');
rbTree.add('B', 'B');
rbTree.add('D', 'D');
rbTree.add('F', 'F');
rbTree.add('J', 'J');
rbTree.add('A', 'A');
rbTree.levelorder(rbTree.root);

下面是 2-3-4樹(shù)2-3樹(shù) 插入結(jié)果的對(duì)比圖:

2-3-4樹(shù)和2-3樹(shù)插入對(duì)比

所以我們一開(kāi)始用紅黑樹(shù)實(shí)現(xiàn)完美平衡的 2-3 樹(shù)墩弯,左右結(jié)點(diǎn)是不會(huì)都是紅色的
現(xiàn)在用紅黑樹(shù)實(shí)現(xiàn) 2-3-4 樹(shù)吩跋,左右結(jié)點(diǎn)的可以同時(shí)是紅色的,這樣的紅黑樹(shù)效率更高渔工。因?yàn)槿绻龅阶笥医Y(jié)點(diǎn)是紅色锌钮,就進(jìn)行顏色翻轉(zhuǎn),還需要對(duì)紅色的父結(jié)點(diǎn)進(jìn)行向上回溯引矩,因?yàn)楦附Y(jié)點(diǎn)染成紅色了梁丘,可能父結(jié)點(diǎn)的父結(jié)點(diǎn)也是紅色,可能需要進(jìn)行結(jié)點(diǎn)旋轉(zhuǎn)或者顏色翻轉(zhuǎn)操作旺韭,所以說(shuō) 2-3-4 樹(shù)式的紅黑樹(shù)效率更高氛谜。

所以回到上面我們提到《算法4》和《算法導(dǎo)論》在實(shí)現(xiàn)上的差異的問(wèn)題,就很好回答了区端,因?yàn)椤端惴?》是用紅黑樹(shù)實(shí)現(xiàn)2-3樹(shù)的值漫,并不是 2-3-4 樹(shù)。但是如果是用紅黑樹(shù)實(shí)現(xiàn) 2-3-4 樹(shù)就和《算法導(dǎo)論》上介紹的紅黑樹(shù)一樣嗎织盼?不一樣杨何。

下面繼續(xù)做一個(gè)測(cè)試,分別往上面紅黑樹(shù)實(shí)現(xiàn)的 2-3-4樹(shù)JDK TreeMap 中插入 [E, D, R, O, S, X]

2-3-4樹(shù)和TreeMap插入對(duì)比

雖然兩棵樹(shù)都是紅黑樹(shù)沥邻,但是卻不一樣危虱。并且TreeMap允許右節(jié)點(diǎn)是紅色,在2-3-4樹(shù)中最多是左右子結(jié)點(diǎn)同時(shí)是紅色的情況唐全,不會(huì)出現(xiàn)左結(jié)點(diǎn)是黑色埃跷,右邊的兄弟結(jié)點(diǎn)是紅色的情況,為什么會(huì)有這樣的差異呢邮利?

從上面的2-3-4樹(shù)的插入邏輯可以看出弥雹,如果右節(jié)點(diǎn)是紅色會(huì)執(zhí)行左旋轉(zhuǎn)操作,所以不會(huì)出現(xiàn)單獨(dú)紅右結(jié)點(diǎn)的情況
也就是說(shuō)只會(huì)出現(xiàn)單獨(dú)的左結(jié)點(diǎn)是紅色的情況近弟,我們把這種形式的紅黑樹(shù)稱之為左傾紅黑樹(shù)(Left Leaning Red Black Tree)缅糟,包括上面的紅黑樹(shù)實(shí)現(xiàn)的完美平衡的2-3樹(shù)也是左傾紅黑樹(shù)

為什么在《算法4》中,作者規(guī)定所有的紅色鏈接都是左鏈接祷愉,這只是人為的規(guī)定窗宦,當(dāng)然也可以是右鏈接,規(guī)定紅鏈接都是左鏈二鳄,可以使用更少的代碼來(lái)實(shí)現(xiàn)黑色平衡赴涵,需要考慮的情況會(huì)更少,就如上面我們介紹的插入操作订讼,我們只需要考慮3中情況即可髓窜。

但是一般意義上的紅黑樹(shù)是不需要維持紅色左傾的這個(gè)性質(zhì)的,所以為什么TreeMap是允許單獨(dú)右紅結(jié)點(diǎn)的

如果還需要維護(hù)左傾情況,這樣的話就更多的操作寄纵,可能還需要結(jié)點(diǎn)旋轉(zhuǎn)和顏色的翻轉(zhuǎn)鳖敷,性能更差一些,雖然也是符合紅黑樹(shù)的性質(zhì)

介紹完了《算法4》上的紅黑樹(shù)程拭,下面就來(lái)分析下一般意義上的紅黑樹(shù)的 插入刪除 操作定踱,也就是《算法導(dǎo)論》上介紹的紅黑樹(shù)。

紅黑樹(shù)插入操作

插入操作有兩種情況是非常簡(jiǎn)單的恃鞋,所以在這里單獨(dú)說(shuō)一下:

case 1. 如果插入的結(jié)點(diǎn)是根結(jié)點(diǎn)崖媚,直接把該結(jié)點(diǎn)設(shè)置為黑色,整個(gè)插入操作結(jié)束

如下圖所示:

case 1

case 2. 如果插入的結(jié)點(diǎn)的父結(jié)點(diǎn)是黑色恤浪,也無(wú)需調(diào)整畅哑,整個(gè)插入操作結(jié)束

如下圖所示:

父節(jié)點(diǎn)是黑色

下面開(kāi)始介紹比較復(fù)雜的情況

紅黑樹(shù)插入操作,我們只需要處理父結(jié)點(diǎn)是紅色的情況水由,因?yàn)橐婚_(kāi)始紅黑樹(shù)肯定是黑色平衡的荠呐,就是因?yàn)橥~子節(jié)點(diǎn)插入元素后可能出現(xiàn)兩個(gè)連續(xù)的紅色的結(jié)點(diǎn)

需要注意的是,我們把新插入的結(jié)點(diǎn)默認(rèn)設(shè)置為紅色绷杜,初始的時(shí)候直秆,正在處理的節(jié)點(diǎn)就是插入的結(jié)點(diǎn),在不斷調(diào)整的過(guò)程中鞭盟,正在處理的節(jié)點(diǎn)會(huì)不斷的變化圾结,且叔叔、爺爺齿诉、父結(jié)點(diǎn)都是相對(duì)于當(dāng)前正在處理的結(jié)點(diǎn)來(lái)說(shuō)的

case 3. 叔叔結(jié)點(diǎn)為紅色筝野,正在處理的節(jié)點(diǎn)可以是左也可以是右結(jié)點(diǎn)

調(diào)整策略:由于父結(jié)點(diǎn)是紅色,叔叔結(jié)點(diǎn)是紅色粤剧,爺爺結(jié)點(diǎn)是黑色歇竟,執(zhí)行顏色翻轉(zhuǎn)操作
然后把當(dāng)前正在處理的結(jié)點(diǎn)設(shè)置為爺爺結(jié)點(diǎn),如果爺爺?shù)母附Y(jié)點(diǎn)是黑色插入操作結(jié)束抵恋,如果是紅色繼續(xù)處理

case 4. 叔叔結(jié)點(diǎn)為黑色焕议,正在處理的結(jié)點(diǎn)是右結(jié)點(diǎn)

調(diào)整策略:由于父結(jié)點(diǎn)是紅色,叔叔結(jié)點(diǎn)為黑色弧关,那么爺爺結(jié)點(diǎn)肯定是黑色
把正在處理的節(jié)點(diǎn)設(shè)置為父結(jié)點(diǎn)盅安,然后左旋,形成Case5情況

case 5. 叔叔結(jié)點(diǎn)為黑色世囊,正在處理的結(jié)點(diǎn)是左孩子

調(diào)整策略:由于父結(jié)點(diǎn)是紅色别瞭,叔叔結(jié)點(diǎn)為黑色,那么爺爺結(jié)點(diǎn)肯定是黑色
把父結(jié)點(diǎn)染黑株憾,爺爺結(jié)點(diǎn)染紅蝙寨,然后爺爺結(jié)點(diǎn)右旋

Case3晒衩、Case4、Case5如果單獨(dú)來(lái)理解的話比較困難墙歪,就算單獨(dú)為每一個(gè)Case畫(huà)圖听系,我覺(jué)得也很難完整的理解,很多博客上都是這種方式箱亿,感覺(jué)不太好理解跛锌。我將這三種情況通過(guò)一張流程圖串聯(lián)起來(lái)弃秆,將這三個(gè)Case形成一個(gè)整體届惋,藍(lán)色箭頭表示正在處理的結(jié)點(diǎn),如下所示:

紅黑樹(shù)的插入操作流程圖

紅黑樹(shù)刪除操作

上面介紹完了紅黑樹(shù)的插入操作菠赚,接下來(lái)看下紅黑樹(shù)的刪除操作

紅黑樹(shù)的刪除操作比插入操作更加復(fù)雜一些

為了描述方便脑豹,我們把正在處理的結(jié)點(diǎn)稱之為 X,父結(jié)點(diǎn)為 P(Parent)衡查,兄弟節(jié)點(diǎn)稱之為 S(Sibling)瘩欺,左侄子稱之為 LN(Left Nephew),右侄子稱之為 RN(Right Nephew)

如果刪除的結(jié)點(diǎn)是黑色拌牲,那么就導(dǎo)致本來(lái)保持黑平衡的紅黑樹(shù)失衡了俱饿,從下圖可以看出結(jié)點(diǎn)P到左子樹(shù)的葉子結(jié)點(diǎn)經(jīng)過(guò)的黑節(jié)點(diǎn)數(shù)量為 4(2+2),到右子樹(shù)的葉子節(jié)點(diǎn)經(jīng)過(guò)的黑色節(jié)點(diǎn)數(shù)量是 5(2+3)塌忽,如下圖所示:

在這里插入圖片描述

紅黑樹(shù)的刪除操作拍埠,如果刪除的是黑色會(huì)導(dǎo)致紅黑樹(shù)就不能保持黑色平衡了,需要進(jìn)行調(diào)整了土居;
如果刪除的是紅色枣购,那么就無(wú)需調(diào)整,直接刪除即可擦耀,因?yàn)闆](méi)有沒(méi)有破壞黑色平衡

刪除結(jié)點(diǎn)后棉圈,無(wú)需調(diào)整的情況

case 1 刪除的結(jié)點(diǎn)是紅色結(jié)點(diǎn),直接刪除即可

case 2 刪除的節(jié)點(diǎn)是黑色眷蜓,如果當(dāng)前處理的節(jié)點(diǎn)X是根結(jié)點(diǎn)

無(wú)論根結(jié)點(diǎn)是什么顏色分瘾,都將根結(jié)點(diǎn)設(shè)置為黑色

case 3 刪除的結(jié)點(diǎn)是黑色,如果當(dāng)前處理的結(jié)點(diǎn)是紅色結(jié)點(diǎn)吁系,將該結(jié)點(diǎn)設(shè)置為黑色

因?yàn)閯h除黑色結(jié)點(diǎn)后德召,就打破了黑色平衡,黑高少了1
所以把一個(gè)紅色節(jié)點(diǎn)設(shè)置為黑色垮抗,這樣黑高又平衡了

刪除節(jié)點(diǎn)后氏捞,需要調(diào)整的情況

正在處理的結(jié)點(diǎn)為X,要?jiǎng)h除的結(jié)點(diǎn)是左結(jié)點(diǎn)冒版,分為4中情況:

case 4 兄弟結(jié)點(diǎn)為紅色

調(diào)整方案:兄弟設(shè)置為黑色液茎,父結(jié)點(diǎn)設(shè)置為紅色,父結(jié)點(diǎn)進(jìn)行左旋轉(zhuǎn)
轉(zhuǎn)化為 case5、case6捆等、case7

case 5 兄弟結(jié)點(diǎn)為黑色滞造,左侄子LN為黑色,右侄子RN為黑色

在這種條件下栋烤,還有兩種情況:父結(jié)點(diǎn)是紅色或黑色谒养,不管是那種情況,調(diào)整方案都是一致的

調(diào)整方案:將兄弟結(jié)點(diǎn)設(shè)置為紅色明郭,把當(dāng)前處理的結(jié)點(diǎn)設(shè)置為父結(jié)P

case 6 兄弟結(jié)點(diǎn)為黑色买窟,左侄子為紅色,右侄子RN為黑色

調(diào)整方案:將左侄子結(jié)點(diǎn)設(shè)置為黑色薯定,兄弟結(jié)點(diǎn)設(shè)置為紅色始绍,兄弟結(jié)點(diǎn)右旋轉(zhuǎn),這樣就轉(zhuǎn)化成了case7

case 7 兄弟結(jié)點(diǎn)為黑色话侄,左侄子不管紅黑亏推,右侄子為紅色

處理方式:兄弟結(jié)點(diǎn)變成父結(jié)點(diǎn)的顏色,然后父結(jié)點(diǎn)設(shè)置黑色年堆,右侄子設(shè)置黑色吞杭,父結(jié)點(diǎn)進(jìn)行左旋轉(zhuǎn)

和插入操作一樣,下面通過(guò)一張流程圖把刪除需要調(diào)整的情況串聯(lián)起來(lái):

紅黑樹(shù)刪除操作流程圖

上面處理的所有情況都是基于正在處理的結(jié)點(diǎn)是左結(jié)點(diǎn)
如果要調(diào)整正在處理的結(jié)點(diǎn)是右節(jié)點(diǎn)的情況变丧,就是上面的處理的鏡像芽狗。插入操作也是同理,所以就省略了

Java TreeMap锄贷、TreeSet源碼分析

TreeMap底層就是用紅黑樹(shù)實(shí)現(xiàn)的译蒂,它在插入后調(diào)整操作主要在fixAfterInsertion方法里,我為每種情況都添加注釋谊却,如下所示:

/** From CLR */
private void fixAfterInsertion(Entry<K,V> x) {
    x.color = RED;

    while (x != null && x != root && x.parent.color == RED) {
        if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
            Entry<K,V> y = rightOf(parentOf(parentOf(x)));
            //-----Case3情況-----
            if (colorOf(y) == RED) {
                setColor(parentOf(x), BLACK);
                setColor(y, BLACK);
                setColor(parentOf(parentOf(x)), RED);
                x = parentOf(parentOf(x));
            } else {
                //-----Case4情況-----
                if (x == rightOf(parentOf(x))) {
                    x = parentOf(x);
                    rotateLeft(x);
                }
                //-----Case5情況-----
                setColor(parentOf(x), BLACK);
                setColor(parentOf(parentOf(x)), RED);
                rotateRight(parentOf(parentOf(x)));
            }
        } else {
            //省略鏡像情況
        }
    }
    root.color = BLACK;
}

它的刪除后調(diào)整操作主要在fixAfterDeletion方法:


/** From CLR */
private void fixAfterDeletion(Entry<K,V> x) {
    while (x != root && colorOf(x) == BLACK) {
        if (x == leftOf(parentOf(x))) {
            Entry<K,V> sib = rightOf(parentOf(x));
            //-----Case4的情況-----
            if (colorOf(sib) == RED) {
                setColor(sib, BLACK);
                setColor(parentOf(x), RED);
                rotateLeft(parentOf(x));
                sib = rightOf(parentOf(x));
            }
            //-----Case5的情況-----
            if (colorOf(leftOf(sib))  == BLACK &&
                colorOf(rightOf(sib)) == BLACK) {
                setColor(sib, RED);
                x = parentOf(x);
            } else {
                //-----Case6的情況-----
                if (colorOf(rightOf(sib)) == BLACK) {
                    setColor(leftOf(sib), BLACK);
                    setColor(sib, RED);
                    rotateRight(sib);
                    sib = rightOf(parentOf(x));
                }
                //-----Case7的情況-----
                setColor(sib, colorOf(parentOf(x)));
                setColor(parentOf(x), BLACK);
                setColor(rightOf(sib), BLACK);
                rotateLeft(parentOf(x));
                x = root;
            }
        } else { // symmetric
            //省略鏡像的情況
        }
    }
    setColor(x, BLACK);
}

TreeSet 底層就是用 TreeMap 來(lái)實(shí)現(xiàn)的柔昼,往TreeSet添加進(jìn)的元素當(dāng)作TreeMap的key,TreeMap的value是一個(gè)常量Object炎辨。掌握了紅黑樹(shù)捕透,對(duì)于這兩個(gè)集合的原理就不難理解了。

最后

本文從一開(kāi)始講的2-3樹(shù)和紅黑樹(shù)的對(duì)應(yīng)關(guān)系碴萧,再到2-3-4樹(shù)和紅黑樹(shù)的對(duì)應(yīng)關(guān)系乙嘀,再到《算法4》和《算法導(dǎo)論》JDK TreeMap在紅黑樹(shù)上的差異
然后詳細(xì)介紹了紅黑樹(shù)的插入、刪除操作破喻,最后分析了下Java中的TreeMap和TreeSet集合類虎谢。

人生當(dāng)如紅黑樹(shù),當(dāng)過(guò)于自喜或過(guò)于自卑的時(shí)候曹质,應(yīng)當(dāng)自我調(diào)整婴噩,尋求平衡擎场。

我很丑,紅黑樹(shù)卻很美几莽。 希望本文對(duì)你有 些許幫助迅办。


參考資料

  1. https://www.cs.princeton.edu/~rs/talks/LLRB/RedBlack.pdf
  2. http://professor.ufabc.edu.br/~jesus.mena/courses/mc3305-2q-2015/AED2-13-redblack-paper.pdf
  3. 《算法4》、《算法導(dǎo)論》

原文鏈接

https://blog.csdn.net/johnny901114/article/details/81046088

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末章蚣,一起剝皮案震驚了整個(gè)濱河市站欺,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌纤垂,老刑警劉巖矾策,帶你破解...
    沈念sama閱讀 219,188評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異洒忧,居然都是意外死亡蝴韭,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門熙侍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人履磨,你說(shuō)我怎么就攤上這事蛉抓。” “怎么了剃诅?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,562評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵巷送,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我矛辕,道長(zhǎng)笑跛,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,893評(píng)論 1 295
  • 正文 為了忘掉前任聊品,我火速辦了婚禮飞蹂,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘翻屈。我一直安慰自己陈哑,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,917評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布伸眶。 她就那樣靜靜地躺著惊窖,像睡著了一般。 火紅的嫁衣襯著肌膚如雪厘贼。 梳的紋絲不亂的頭發(fā)上界酒,一...
    開(kāi)封第一講書(shū)人閱讀 51,708評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音嘴秸,去河邊找鬼毁欣。 笑死售担,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的署辉。 我是一名探鬼主播族铆,決...
    沈念sama閱讀 40,430評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼哭尝!你這毒婦竟也來(lái)了哥攘?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,342評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤材鹦,失蹤者是張志新(化名)和其女友劉穎逝淹,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體桶唐,經(jīng)...
    沈念sama閱讀 45,801評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡栅葡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,976評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了尤泽。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片欣簇。...
    茶點(diǎn)故事閱讀 40,115評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖坯约,靈堂內(nèi)的尸體忽然破棺而出熊咽,到底是詐尸還是另有隱情,我是刑警寧澤闹丐,帶...
    沈念sama閱讀 35,804評(píng)論 5 346
  • 正文 年R本政府宣布横殴,位于F島的核電站,受9級(jí)特大地震影響卿拴,放射性物質(zhì)發(fā)生泄漏衫仑。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,458評(píng)論 3 331
  • 文/蒙蒙 一堕花、第九天 我趴在偏房一處隱蔽的房頂上張望文狱。 院中可真熱鬧,春花似錦航徙、人聲如沸如贷。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,008評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)杠袱。三九已至,卻和暖如春窝稿,著一層夾襖步出監(jiān)牢的瞬間楣富,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,135評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工伴榔, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留纹蝴,地道東北人庄萎。 一個(gè)月前我還...
    沈念sama閱讀 48,365評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像塘安,于是被迫代替她去往敵國(guó)和親糠涛。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,055評(píng)論 2 355