數(shù)據(jù)機構(gòu)與算法--索引優(yōu)先隊列

數(shù)據(jù)機構(gòu)與算法--索引優(yōu)先隊列

圖片來自nullzx的博客園

索引優(yōu)先隊列热某,用一個索引數(shù)組保存了元素在數(shù)組中的位置延旧。在插入隊列中時,可看作將一個整數(shù)和一個對象相關(guān)聯(lián)谒府,使得我們可以引用隊列中的元素粗梭。比如在Dijkstra算法中就用到了索引優(yōu)先隊列争便,他將頂點(整數(shù)表示)和源點到該頂點的最短路徑長度關(guān)聯(lián)起來,每次刪除最小元素断医,都能得到與之關(guān)聯(lián)的那個整數(shù)滞乙;如果該整數(shù)已經(jīng)被關(guān)聯(lián)了,還可以更新與該整數(shù)關(guān)聯(lián)的對象鉴嗤≌镀簦可以看出,索引優(yōu)先隊列和優(yōu)先隊列比起來醉锅,操作數(shù)組里面的元素更加方便了——這就是關(guān)聯(lián)了索引帶來的好處兔簇。

我們使用Key[] keys保存對象,int[] pq保存對象在數(shù)組中的位置硬耍,比如pq[1] = 5垄琐,那么keys[pq[1]] = keys[5]表示整數(shù)5和對象關(guān)聯(lián),而這個整數(shù)5存放于pq索引為1的位置经柴;用一個int[] qp保存pq的逆序狸窘,即如果pq[i] = j(表示數(shù)組pq索引為i的位置存放了一個被關(guān)聯(lián)的整數(shù)j),則有qp[j] = i口锭,因此qp保存的是被關(guān)聯(lián)整數(shù)j在數(shù)組pq中的索引。易知pq[qp[j]] = j; qp[pq[i]] = i

要注意的是介杆,pq數(shù)組存放的關(guān)聯(lián)整數(shù)是連續(xù)的鹃操,而qp和keys數(shù)組中存放的元素不是連續(xù)的,他們的位置是一一對應(yīng)的春哨。如果整數(shù)i還沒有被關(guān)聯(lián)荆隘,總是令qp[i] = -1,因此對應(yīng)地keys[i] = null

自始至終keys數(shù)組中的元素位置不會發(fā)生變化赴背,這就是說所有上浮下沉操作后椰拒,keys中元素的相對位置都不會變化,變化的只是與之關(guān)聯(lián)的索引pq還有qp而已凰荚。(反正都能通過pq數(shù)組中存放的關(guān)聯(lián)整數(shù)快速找到keys數(shù)組中的元素燃观,所以只改變pq和qp中元素的相對位置足矣)

image

下圖是將與整數(shù)3關(guān)聯(lián)的對象替換成“a”.

image

這是一個小頂堆,pq是優(yōu)先隊列便瑟,但是它優(yōu)先級的順序并不是按照其關(guān)聯(lián)的整數(shù)來排列的缆毁,而是按照關(guān)聯(lián)整數(shù)對應(yīng)的對象的大小來排列,即keys[pq[i]]到涂。因此可以寫出用于比較兩個元素的less或者greater方法脊框,我們先實現(xiàn)基于小頂堆的索引優(yōu)先隊列颁督,它可以快速找到或者刪除最大元素,所以greater方法如下

private boolean greater(int i, int j) {
    return keys[pq[i]].compareTo(keys[pq[j]]) > 0;
}

可以刪除最小元素的IndexMinPQ

我們的所有關(guān)鍵方法幾乎都以greater方法為基礎(chǔ)浇雹,理解它尤為重要沉御。以下是完整實現(xiàn)。

package Chap9;

import java.util.Arrays;
import java.util.NoSuchElementException;

public class IndexMinPQ<Key extends Comparable<Key>> {
    private int N;
    private int[] pq; // 索引二叉堆昭灵,按照慣例從1開始
    private int[] qp; // 逆序吠裆,滿足qp[pq[i]] = pq[qp[i]] = i
    private Key[] keys;

    public IndexMinPQ(int maxN) {
        // 可存放范圍為[0, maxN]
        keys = (Key[]) new Comparable[maxN + 1];
        // 索引二叉堆,存放范圍為[1, maxN]
        pq = new int[maxN + 1];
        // 可存放范圍為[0, maxN]
        qp = new int[maxN + 1];
        // 剛開始沒有關(guān)聯(lián)任何整數(shù),都設(shè)置為-1
        Arrays.fill(qp, -1);
    }

    // 針對是pq中的索引i虎锚、j硫痰,但是實際引用的是keys中對應(yīng)的元素
    private boolean greater(int i, int j) {
        return keys[pq[i]].compareTo(keys[pq[j]]) > 0;
    }

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

    public int size() {
        return N;
    }

    public boolean contains(int k) {
        return qp[k] != -1;
    }

    public void insert(int k, Key key) {
        if (!contains(k)) {
            N++;
            pq[N] = k;
            qp[k] = N;
            keys[k] = key;
            swim(N);
        }
    }

    // 給整數(shù)k重新關(guān)聯(lián)一個對象
    public void replace(int k, Key key) {
        keys[k] = key;
        // 由于和k關(guān)聯(lián)的新key可能大于原來的key(此時需要下沉),也有可能小于原來的key(此時需要上复芑ぁ)效斑,為了簡化代碼,既上浮又下沉柱徙,就囊括了這兩種可能缓屠。
        swim(qp[k]);
        sink(qp[k]);
    }

    // 返回最小元素
    public Key min() {
        return keys[pq[1]];
    }

    // 最小元素的關(guān)聯(lián)整數(shù)
    public int minIndex() {
        return pq[1];
    }

    public int delMin() {
        if (isEmpty()) {
            throw new NoSuchElementException("隊列已經(jīng)為空,不能執(zhí)行刪除护侮!");
        }
        int indexOfMin = pq[1];
        // 堆頂和最后一個元素交換
        swap(1, N--);
        sink(1);
        // 最后一個元素置為空
        keys[indexOfMin] = null;
        // 同時關(guān)聯(lián)整數(shù)pq[N]在pq中的的索引設(shè)置為-1敌完,表示還沒有對象與該整數(shù)關(guān)聯(lián)
        qp[indexOfMin] = -1;

        return indexOfMin;
    }

    public void delete(int k) {
        if (!contains(k)) {
            throw new NoSuchElementException("沒有元素與" + k + "關(guān)聯(lián)!");
        }
        // index為整數(shù)k在pq中的位置
        int index = qp[k];

        swap(index, N--);
        // 這里一定要先上浮下沉后再將元素置空羊初,因為swim方法沒有N的限制滨溉,在沒有交換元素的情況下,即刪除的就是pq中最后一個元素长赞,如果先置空, 會在greater方法中引發(fā)空指針
        // 而sink方法有N的限制晦攒,先置空后置空都沒有影響,2k <= N會限制它進(jìn)入循環(huán)得哆,避免了空指針
        swim(index);
        sink(index);
        keys[k] = null;
        qp[k] = -1;
    }

    public Key keyOf(int k) {
        if (contains(k)) {
            return keys[k];
        }
        // 沒有與k關(guān)聯(lián)就返回null
        return null;
    }

    private void swap(int i, int j) {
        int temp = pq[i];
        pq[i] = pq[j];
        pq[j] = temp;
        // 還要更新qp
        qp[pq[i]] = i;
        qp[pq[j]] = j;
    }

    private void swim(int k) {
        // k = 1說明當(dāng)前元素浮到了根結(jié)點脯颜,它沒有父結(jié)點可以比較,也不能上浮了贩据,所以k <= 1時候推出循環(huán)
        while (k > 1 && greater(k / 2, k)) {
            swap(k / 2, k);
            // 上浮后栋操,成為父結(jié)點,所以下標(biāo)變成原父結(jié)點
            k = k / 2;
        }
    }

    private void sink(int k) {
        // 父結(jié)點的位置k最大值為 N/2,若k有左子結(jié)點無右子結(jié)點饱亮,那么2k = N矾芙;若兩個子結(jié)點都有,那么2k + 1 = N
        // 有可能位置k只有左子結(jié)點近上,依然要比較蠕啄,用2k + 1 <= N這個的條件不會執(zhí)行比較,所以用2k <= N條件
        while (2 * k <= N) {
            int j = 2 * k;
            // 可以取j = N -1,greater(N -1, N);由于下標(biāo)從1開始,所以pq[N]是有元素的
            if (j < N && greater(j, j + 1)) {
                // 右子結(jié)點比左子結(jié)點大 歼跟,取右子結(jié)點的下標(biāo)
                j++;
            }
            // 左子結(jié)點或者右子結(jié)點和父結(jié)點比較
            // 如果pq[k] >= pq[j]和媳,即父結(jié)點大于等于較大子結(jié)點時,停止下沉
            if (!greater(k, j)) {
                break;
            }
            // 否則交換
            swap(k, j);
            // 下沉后哈街,下標(biāo)變成與之交換的元素下標(biāo)
            k = j;
        }
    }

    public static void main(String[] args) {
        IndexMinPQ<String> indexMinPQ = new IndexMinPQ<>(20);
        indexMinPQ.insert(5, "E");
        indexMinPQ.insert(7, "G");
        indexMinPQ.insert(2, "B");
        indexMinPQ.insert(1, "A");
        if (indexMinPQ.contains(7)) {
            indexMinPQ.replace(7, "Z");
        }

        System.out.println(indexMinPQ.min()); // A
        System.out.println(indexMinPQ.delMin()); // 1
        System.out.println(indexMinPQ.delMin());// 2
        System.out.println(indexMinPQ.minIndex()); // 5
        System.out.println(indexMinPQ.keyOf(7)); // Z
        indexMinPQ.delete(7);

    }
}

swap方法不僅交換了pq中的元素——即關(guān)聯(lián)的整數(shù)留瞳,也要同時更新qp,保持qp[pq[i]] = i這樣的關(guān)系骚秦。swim和sink方法沒有改變她倘,要熟知這兩個方法操作的是二叉堆pq,而pq中關(guān)聯(lián)的整數(shù)映射著真正的數(shù)據(jù)元素作箍。因此pq[1]存放的是和最小元素關(guān)聯(lián)的整數(shù)硬梁,通過keys[pq[1]]就可以返回最小元素。這句話解釋了minminIndex方法的實現(xiàn)胞得。

我們來看insert方法荧止,先判斷要關(guān)聯(lián)的整數(shù)k是不是已經(jīng)被關(guān)聯(lián)了,沒有關(guān)聯(lián)時才能進(jìn)行下面的操作阶剑,和優(yōu)先隊列一樣跃巡,在二叉堆pq的末尾插入,同時qp數(shù)組也要賦值牧愁,然后在keys中的關(guān)聯(lián)整數(shù)k處存入元素素邪,最后上浮操作恢復(fù)堆有序狀態(tài);如果整數(shù)k已經(jīng)被關(guān)聯(lián)猪半,即replace方法兔朦,用一個新的對象和這個整數(shù)關(guān)聯(lián),這里注意磨确,由于和k關(guān)聯(lián)的新key可能大于原來的key(此時需要下沉)沽甥,也有可能小于原來的key(此時需要上浮)俐填,為了簡化代碼安接,既上浮又下沉翔忽,就囊括了這兩種可能英融。

delete方法,先找到關(guān)聯(lián)整數(shù)k在pq中的位置歇式,然后將其與最后一個交換位置驶悟,同時N減去1。之后對換過去的元素作上浮下沉操作材失,然后才在keys中將k位置的元素置空痕鳍,一定要先上浮下沉后才置空,因為swim方法沒有N的限制,在沒有交換元素的情況下笼呆,即刪除的就是pq中最后一個元素熊响,如果先置空, 會在greater方法中引發(fā)空指針

delMin方法刪除最小元素同時返回與之關(guān)聯(lián)的整數(shù)诗赌。最小元素位于堆頂即pq[1]汗茄,這個值就是最小元素關(guān)聯(lián)的整數(shù)。之后刪除最小元素的操作就和delete類似了铭若,將最小元素的索引pq[1]和最后一個元素交換洪碳,然后下沉(在堆頂無需上浮)恢復(fù)堆有序狀態(tài)叼屠。

keyOf(int k)返回與整數(shù)k關(guān)聯(lián)的元素瞳腌。

各個方法在最壞情況下的時間復(fù)雜度如下所示

image

可以刪除最大元素的IndexMaxPQ

IndexMaxPQ的實現(xiàn)可以通過簡單修改IndexMinPQ得到。將greater方法改成less镜雨,然后實現(xiàn)中所有g(shù)reater方法替換成less即可

private boolean less(int i, int j) {
    return keys[pq[i]].compareTo(keys[pq[j]]) < 0;
}

實現(xiàn)如下

package Chap9;

import java.util.Arrays;
import java.util.NoSuchElementException;

public class IndexMaxPQ<Key extends Comparable<Key>> {

    private int N;
    private int[] pq; // 索引二叉堆嫂侍,按照慣例從1開始
    private int[] qp; // 逆序,滿足qp[pq[i]] = pq[qp[i]] = i
    private Key[] keys;

    public IndexMaxPQ(int maxN) {
        // 可存放范圍為[0, maxN]
        keys = (Key[]) new Comparable[maxN + 1];
        // 索引二叉堆,存放范圍為[1, maxN]
        pq = new int[maxN + 1];
        // 可存放范圍為[0, maxN]
        qp = new int[maxN + 1];
        // 剛開始沒有關(guān)聯(lián)任何整數(shù)冷离,都設(shè)置為-1
        Arrays.fill(qp, -1);
    }

    // 針對是pq中的索引i吵冒、j,但是實際引用的是keys中對應(yīng)的元素
    private boolean less(int i, int j) {
        return keys[pq[i]].compareTo(keys[pq[j]]) < 0;
    }

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

    public int size() {
        return N;
    }

    public boolean contains(int k) {
        return qp[k] != -1;
    }

    public void insert(int k, Key key) {
        if (!contains(k)) {
            N++;
            pq[N] = k;
            qp[k] = N;
            keys[k] = key;
            swim(N);
        }
    }

    // 給整數(shù)k重新關(guān)聯(lián)一個對象
    public void replace(int k, Key key) {
        keys[k] = key;
        // 由于和k關(guān)聯(lián)的新key可能大于原來的key(此時需要下沉)西剥,也有可能小于原來的key(此時需要上副云堋),為了簡化代碼瞭空,既上浮又下沉揪阿,就囊括了這兩種可能。
        swim(qp[k]);
        sink(qp[k]);
    }

    // 返回最小元素
    public Key max() {
        return keys[pq[1]];
    }

    // 最小元素的關(guān)聯(lián)整數(shù)
    public int maxIndex() {
        return pq[1];
    }

    public int delMax() {
        if (isEmpty()) {
            throw new NoSuchElementException("隊列已經(jīng)為空咆畏,不能執(zhí)行刪除南捂!");
        }
        int indexOfMax = pq[1];
        // 堆頂和最后一個元素交換
        swap(1, N--);
        sink(1);
        // 最后一個元素置為空
        keys[indexOfMax] = null;
        // 同時關(guān)聯(lián)整數(shù)pq[N]在pq中的的索引設(shè)置為-1,表示還沒有對象與該整數(shù)關(guān)聯(lián)
        qp[indexOfMax] = -1;

        return indexOfMax;
    }

    public void delete(int k) {
        if (!contains(k)) {
            throw new NoSuchElementException("沒有元素與" + k + "關(guān)聯(lián)旧找!");
        }
        // index為整數(shù)k在pq中的位置
        int index = qp[k];

        swap(index, N--);
        // 這里一定要先上浮下沉后再將元素置空溺健,因為swim方法沒有N的限制,在沒有交換元素的情況下钮蛛,即刪除的就是pq中最后一個元素鞭缭,如果先置空, 會在greater方法中引發(fā)空指針
        // 而sink方法有N的限制,先置空后置空都沒有影響魏颓,2k <= N會限制它進(jìn)入循環(huán)岭辣,避免了空指針
        swim(index);
        sink(index);
        keys[k] = null;
        qp[k] = -1;
    }

    public Key keyOf(int k) {
        if (contains(k)) {
            return keys[k];
        }
        // 沒有與k關(guān)聯(lián)就返回null
        return null;
    }

    private void swap(int i, int j) {
        int temp = pq[i];
        pq[i] = pq[j];
        pq[j] = temp;
        // 還要更新qp
        qp[pq[i]] = i;
        qp[pq[j]] = j;
    }

    private void swim(int k) {
        // k = 1說明當(dāng)前元素浮到了根結(jié)點,它沒有父結(jié)點可以比較甸饱,也不能上浮了沦童,所以k <= 1時候推出循環(huán)
        while (k > 1 && less(k / 2, k)) {
            swap(k / 2, k);
            // 上浮后仑濒,成為父結(jié)點,所以下標(biāo)變成原父結(jié)點
            k = k / 2;
        }
    }

    private void sink(int k) {
        // 父結(jié)點的位置k最大值為 N/2,若k有左子結(jié)點無右子結(jié)點偷遗,那么2k = N墩瞳;若兩個子結(jié)點都有,那么2k + 1 = N
        // 有可能位置k只有左子結(jié)點氏豌,依然要比較矗烛,用2k + 1 <= N這個的條件不會執(zhí)行比較,所以用2k <= N條件
        while (2 * k <= N) {
            int j = 2 * k;
            // 可以取j = N -1,greater(N -1, N);由于下標(biāo)從1開始箩溃,所以pq[N]是有元素的
            if (j < N && less(j, j + 1)) {
                // 右子結(jié)點比左子結(jié)點大 瞭吃,取右子結(jié)點的下標(biāo)
                j++;
            }
            // 左子結(jié)點或者右子結(jié)點和父結(jié)點比較
            // 如果pq[k] >= pq[j],即父結(jié)點大于等于較大子結(jié)點時涣旨,停止下沉
            if (!less(k, j)) {
                break;
            }
            // 否則交換
            swap(k, j);
            // 下沉后歪架,下標(biāo)變成與之交換的元素下標(biāo)
            k = j;
        }
    }

    public static void main(String[] args) {
        IndexMaxPQ<String> indexMaxPQ = new IndexMaxPQ<>(20);
        indexMaxPQ.insert(5, "E");
        indexMaxPQ.insert(7, "G");
        indexMaxPQ.insert(2, "B");
        indexMaxPQ.insert(1, "A");
        if (indexMaxPQ.contains(7)) {
            indexMaxPQ.replace(7, "Z");
        }

        System.out.println(indexMaxPQ.max()); // Z
        System.out.println(indexMaxPQ.delMax()); // 7
        System.out.println(indexMaxPQ.delMax());// 5
        System.out.println(indexMaxPQ.maxIndex()); // 2
        System.out.println(indexMaxPQ.keyOf(1)); // A
        indexMaxPQ.delete(1);

    }
}

使用優(yōu)先隊列的多項歸并

下面的例子使用IndexMinPQ解決了多向歸并的問題:它將多個已經(jīng)有序的輸入流歸并成一個有序的輸出流。無論輸入流有多長霹陡,都可以將其全部讀入并排序(并不是一次性讀入內(nèi)存的和蚪,我們將看到任何時刻隊列中只存在每個輸入流的一個元素而已)

package Chap9;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;

public class Multiway {
    public static void merge(InputStream[] streams) throws IOException {
        int N = streams.length;
        // 為每個輸入流關(guān)聯(lián)一個整數(shù)
        IndexMinPQ<String> pq = new IndexMinPQ<>(N);
        // 從每個流中讀取一個字符,因為每個流都已經(jīng)有序烹棉,所以其中必然有最小元素
        for (int i = 0; i < N; i++) {
            int ch;
            if ((ch=streams[i].read()) != -1) {
                pq.insert(i, String.valueOf((char)ch));
            }
        }

        while (!pq.isEmpty()) {
            // 不斷選出最小元素打印
            System.out.print(pq.min());
            // 關(guān)聯(lián)這個整數(shù)的對象被刪除攒霹,從關(guān)聯(lián)該整數(shù)的剩余流中再讀取一個字符,并加入到索引優(yōu)先隊列中
            int i = pq.delMin();
            int ch;
            if ((ch=streams[i].read()) != -1) {
                pq.insert(i, String.valueOf((char)ch));
            }
        }
        System.out.println();
    }

    public static void main(String[] args) {

        InputStream stream1 = new ByteArrayInputStream("ACHYZ".getBytes());
        InputStream stream2 = new ByteArrayInputStream("BCRXY".getBytes());
        InputStream stream3 = new ByteArrayInputStream("ADPQS".getBytes());
        InputStream[] streams = {stream1, stream2 ,stream3};
        try {
            merge(streams); // AABCCDHPQRSXYYZ
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

上個例子中有三個輸入流浆洗,merge方法剛開始給這三個輸入流分別關(guān)聯(lián)一個整數(shù)催束,然后從這三個輸入流中分別讀取一個字符到索引優(yōu)先隊列中。之后就打印三個字符中的最小者伏社,A被打印然后刪除抠刺,返回與A關(guān)聯(lián)的整數(shù),然后從與該整數(shù)關(guān)聯(lián)的剩余流中再讀取一個字符摘昌,并加入到索引優(yōu)先隊列中速妖。此時的狀態(tài)又回到每個輸入流的元素都有一個存在于索引優(yōu)先隊列中。隊列中始終保持只有三個元素聪黎,一直選出并刪除最小元素罕容,就完成了多向歸并排序。

運行上面的代碼會輸出AABCCDHPQRSXYYZ稿饰。將三個序列歸并排序成功锦秒!對于任意個輸入流,merge方法都可以應(yīng)對湘纵,并且隊列所需空間和輸入流的個數(shù)成正比脂崔,而不是和所有輸入流的元素個數(shù)成正比滤淳,這可以在歸并排序時節(jié)約大量內(nèi)存梧喷。


by @sunhaiyu

2017.11.7

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子铺敌,更是在濱河造成了極大的恐慌汇歹,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,284評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件偿凭,死亡現(xiàn)場離奇詭異产弹,居然都是意外死亡,警方通過查閱死者的電腦和手機弯囊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評論 3 395
  • 文/潘曉璐 我一進(jìn)店門痰哨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人匾嘱,你說我怎么就攤上這事斤斧。” “怎么了霎烙?”我有些...
    開封第一講書人閱讀 164,614評論 0 354
  • 文/不壞的土叔 我叫張陵撬讽,是天一觀的道長。 經(jīng)常有香客問我悬垃,道長游昼,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,671評論 1 293
  • 正文 為了忘掉前任尝蠕,我火速辦了婚禮烘豌,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘看彼。我一直安慰自己扇谣,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,699評論 6 392
  • 文/花漫 我一把揭開白布闲昭。 她就那樣靜靜地躺著罐寨,像睡著了一般。 火紅的嫁衣襯著肌膚如雪序矩。 梳的紋絲不亂的頭發(fā)上鸯绿,一...
    開封第一講書人閱讀 51,562評論 1 305
  • 那天,我揣著相機與錄音簸淀,去河邊找鬼瓶蝴。 笑死,一個胖子當(dāng)著我的面吹牛租幕,可吹牛的內(nèi)容都是我干的舷手。 我是一名探鬼主播,決...
    沈念sama閱讀 40,309評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼劲绪,長吁一口氣:“原來是場噩夢啊……” “哼男窟!你這毒婦竟也來了盆赤?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,223評論 0 276
  • 序言:老撾萬榮一對情侶失蹤歉眷,失蹤者是張志新(化名)和其女友劉穎牺六,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體汗捡,經(jīng)...
    沈念sama閱讀 45,668評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡淑际,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,859評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了扇住。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片春缕。...
    茶點故事閱讀 39,981評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖艘蹋,靈堂內(nèi)的尸體忽然破棺而出淡溯,到底是詐尸還是另有隱情,我是刑警寧澤簿训,帶...
    沈念sama閱讀 35,705評論 5 347
  • 正文 年R本政府宣布咱娶,位于F島的核電站罩抗,受9級特大地震影響摔蓝,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜玩徊,卻給世界環(huán)境...
    茶點故事閱讀 41,310評論 3 330
  • 文/蒙蒙 一的榛、第九天 我趴在偏房一處隱蔽的房頂上張望琼了。 院中可真熱鬧,春花似錦夫晌、人聲如沸雕薪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽所袁。三九已至,卻和暖如春凶掰,著一層夾襖步出監(jiān)牢的瞬間燥爷,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評論 1 270
  • 我被黑心中介騙來泰國打工懦窘, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留前翎,地道東北人。 一個月前我還...
    沈念sama閱讀 48,146評論 3 370
  • 正文 我出身青樓畅涂,卻偏偏與公主長得像港华,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子午衰,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,933評論 2 355

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