圖論(7):圖的遍歷 - 廣度優(yōu)先和深度優(yōu)先算法

定義

從圖中某一頂點(diǎn)出發(fā)訪遍圖中其余頂點(diǎn)碉钠,且使每一個頂點(diǎn)僅被訪問一次,這個訪問的過程叫做圖的遍歷(Traversing Graph)。且圖的遍歷算法是一個比較基礎(chǔ)的算法帕识,前面我們介紹的有向無環(huán)圖的依賴排序(拓?fù)渑判颍┥巍㈥P(guān)鍵路徑等算法都需要基于該算法胰柑。
通常,有兩條遍歷圖的路徑:廣度優(yōu)先搜索深度優(yōu)先搜索爬泥,且對無向圖和有向圖都適用柬讨。

另外,由于Guava中的Graph模塊已對這兩種圖的遍歷算法進(jìn)行了實現(xiàn)急灭,且其代碼是我所見過最完美的實現(xiàn)了姐浮。因此,本篇文章不打算重新實現(xiàn)一個遍歷算法葬馋,而是以Guava的實現(xiàn)為基礎(chǔ)進(jìn)行算法分析和驗證卖鲤。它的具體實現(xiàn)代碼文件為:https://github.com/google/guava/blob/master/android/guava/src/com/google/common/graph/Traverser.java

廣度優(yōu)先遍歷

也稱為廣度優(yōu)先搜索(Breadth First Search)肾扰,它類似于樹的分層遍歷算法(按樹的深度來遍歷,深度為1的節(jié)點(diǎn)蛋逾、深度為2的節(jié)點(diǎn)...)集晚。其定義如下:
假設(shè)從圖中某個頂點(diǎn)v出發(fā),在訪問了v之后依次訪問v的各個未曾訪問過的鄰接點(diǎn)区匣,然后分別從這些鄰接點(diǎn)出發(fā)并依次訪問它們的鄰接點(diǎn)偷拔,并使“先被訪問的頂點(diǎn)鄰接點(diǎn)”先于“后被訪問的頂點(diǎn)的鄰接點(diǎn)”被訪問,直到圖中所有所有已被訪問的頂點(diǎn)的鄰接點(diǎn)都被訪問到亏钩。若此時圖中尚有頂點(diǎn)未被訪問莲绰,則另選圖中一個未曾被訪問的頂點(diǎn)作起始點(diǎn)重復(fù)上述過程,直至圖中所有頂點(diǎn)均被訪問到為止姑丑。

換句話說蛤签,廣度優(yōu)先遍歷的過程是以v為起始點(diǎn),由近至遠(yuǎn)栅哀,依次訪問和v有路徑相通且路徑長度為1,2,...的頂點(diǎn)的過程震肮。

下面演示對示例圖的廣度優(yōu)先遍歷:假設(shè)從起始點(diǎn)v1開始遍歷,首先訪問v1和v1的鄰接點(diǎn)v2和v3留拾,然后依次訪問v2的鄰接點(diǎn)v4和v5戳晌,及v3的鄰接點(diǎn)v6和v7,最后訪問v4的鄰接點(diǎn)v8痴柔。于是得到節(jié)點(diǎn)的線性遍歷順序為:v1 -> v2 -> v3 -> v4 -> v5 -> v6 -> v7 -> v8沦偎,即示例圖中紅色箭頭線為其廣度優(yōu)先遍歷順序。

廣度優(yōu)先遍歷(紅色箭頭線及序號為遍歷順序)

算法分析

從演示示例的推演中竞帽,我們可以發(fā)現(xiàn)其訪問順序恰好是一個隊列的出隊和入隊的過程:出隊的節(jié)點(diǎn)即為當(dāng)前訪問的節(jié)點(diǎn)扛施,緊接著將其所有的鄰接點(diǎn)入隊,為下次迭代時將要訪問的預(yù)備節(jié)點(diǎn)屹篓。

Guava的實現(xiàn)思路基本也是如此疙渣,下面我們看看它的實現(xiàn)方式:
首先,它定義了一個抽象的廣度優(yōu)先遍歷接口堆巧,以便有多種實現(xiàn)方式(下面它還有一個樹狀圖的遍歷優(yōu)化實現(xiàn))妄荔,更重要的是它將遍歷的過程封裝在一個迭代器(Iterable)中返回了,僅在調(diào)用方通過next()訪問返回結(jié)果時才動態(tài)輸出其遍歷順序谍肤,個人覺得這是它實現(xiàn)的一大亮點(diǎn)啦租。這樣做的好處是,實際的調(diào)用過程并不消耗cpu時間(僅僅是new了一個特定類型的Iterator)荒揣,而是在調(diào)用完成后在使用其結(jié)果做其他運(yùn)算時(比如輸出或者將其作為其他功能的輸入等)才耗費(fèi)計算時間篷角。一般,我們通常的做法是函數(shù)完成遍歷運(yùn)算然后返回運(yùn)算結(jié)果(List<N>)系任,如果后續(xù)需要利用該結(jié)果進(jìn)行其他運(yùn)算時再循環(huán)遍歷結(jié)果集恳蹲,亦或者直接在在遍歷算法的迭代中直接處理其結(jié)果虐块,而迭代器明顯優(yōu)于這兩種做法盈滴。

廣度優(yōu)先遍歷接口定義為:

/**
 * 
 * @param startNode: 遍歷的起始節(jié)點(diǎn)
 * @return 返回一個Iterable作為節(jié)點(diǎn)的順序
 */
public abstract Iterable<N> breadthFirst(N startNode);

由于接口返回的是一個Iterable苍柏,那么它的實現(xiàn)肯定就是創(chuàng)建了一個Iterable了,然后實現(xiàn)它的iterator()接口:

@Override
public Iterable<N> breadthFirst(final N startNode) {
    /**
     * 創(chuàng)建一個匿名的Iterable隆嗅,并實現(xiàn)iterator()接口
     */
    return new Iterable<N>() {
        @Override
        public Iterator<N> iterator() {
            /**
             * 返回一個自定義的Iterator
             */
            return new BreadthFirstIterator(startNode);
        }
    };
}

該自定義迭代器BreadthFirstIterator中错忱,其主體實現(xiàn)在迭代函數(shù)next()中儡率,每迭代一次(調(diào)用next()函數(shù)一次)返回一個以廣度優(yōu)先遍歷為順序的一個節(jié)點(diǎn):

/**
 * 聲明為private私有, 可對外隱藏其內(nèi)部實現(xiàn),調(diào)用者僅需要
 * 知道是一個Iterator即可
 */
private final class BreadthFirstIterator extends Iterator<N> {
    //構(gòu)建節(jié)點(diǎn)順序的隊列:執(zhí)行入隊和出隊操作
    private final Queue<N> queue = new ArrayDeque<>();

    //用于記錄節(jié)點(diǎn)是否訪問標(biāo)記以清,避免重復(fù)訪問
    private final Set<N> visited = new HashSet<>();

    /**
     * 構(gòu)造函數(shù)時儿普,首先從起始點(diǎn)入隊開始
     * @param root
     */
    BreadthFirstIterator(N root) {
        queue.add(root);
        visited.add(root);
    }

    @Override
    public boolean hasNext() {
        /**
         * 當(dāng)隊列為空時,則遍歷完成玖媚,即沒有后續(xù)節(jié)點(diǎn)了箕肃。
         */
        return !queue.isEmpty();
    }

    @Override
    public N next() {
        //隊頭節(jié)點(diǎn)出隊,作為當(dāng)前迭代的訪問節(jié)點(diǎn)
        N current = queue.remove();
        /**
         * 依次將當(dāng)前節(jié)點(diǎn)的后繼節(jié)點(diǎn)入隊今魔,為下一次迭代做準(zhǔn)備
         */
        for (N neighbor : graph.successors(current)) {
            //add()返回true時(表示第一次加入,即是第一次訪問)障贸,則入隊
            if (visited.add(neighbor)) {
                queue.add(neighbor);
            }
        }
        return current;
    }
}

上面給出了廣度優(yōu)先遍歷算法的實現(xiàn)错森,下面寫一個簡單demo驗證其實現(xiàn)結(jié)果:

//構(gòu)建示例圖所示的圖結(jié)構(gòu)(無向圖)
MutableGraph<String> graph = GraphBuilder.undirected()
    .nodeOrder(ElementOrder.<String>natural()).build();

graph.putEdge(V1, V2);
graph.putEdge(V1, V3);
graph.putEdge(V2, V4);
graph.putEdge(V2, V5);
graph.putEdge(V3, V6);
graph.putEdge(V3, V7);
graph.putEdge(V4, V8);
graph.putEdge(V5, V8);
graph.putEdge(V6, V7);

//測試breadthFirst()接口,從V1開始遍歷
Iterable<String> bfs = Traverser.forGraph(graph).breadthFirst(V1);

//輸出遍歷結(jié)果: for循環(huán)(forEach)默認(rèn)實現(xiàn)iterator的遍歷next()
for (String node : iterable) {
    print(node);
}

輸出順序為:

bfs graph: {V1,V3,V2,V6,V7,V5,V4,V8}

注:雖然該順序與示例圖的紅色箭頭線標(biāo)識的順序有所不同篮洁,但仍滿足廣度優(yōu)先遍歷的順序涩维,其差異主要是在選擇當(dāng)前節(jié)點(diǎn)的后繼節(jié)點(diǎn)時 遍歷后繼節(jié)點(diǎn)的順序不同。
而該順序不同的主要原因是由于無向圖鄰接點(diǎn)(successors()返回的Set<T>結(jié)果)的存儲結(jié)構(gòu)UndirectedGraphConnections采用的是HashMap袁波,然后通過HashMapKeySet()(對應(yīng)的是HashSet<T>)返回的節(jié)點(diǎn)集瓦阐,但由于HashSet是不是有序的,所以導(dǎo)致最終的結(jié)果并沒有按預(yù)先的順序篷牌,但結(jié)果整體上滿足遍歷順序的要求睡蟋。

關(guān)于HashMap的順序問題,我做了如下測試:

//返回V1的后繼節(jié)點(diǎn)集
Set<String> successors = graph.successors(V1);
print(success);

//測試HashMap的節(jié)點(diǎn)順序
Map<String, Integer> testMap = new HashMap<>();
testMap.put(V1, 0);
testMap.put(V2, 0);
testMap.put(V3, 0);
print(testMap.keySet());

測試結(jié)果如下枷颊,剛好重現(xiàn)了遍歷順序的差異:

successor: {V3,V2}
Map KeySet() order: {V3,V2,V1}

下面我繼續(xù)來看深度優(yōu)先遍歷:

深度優(yōu)先遍歷

也稱為深度優(yōu)先搜索(Depth First Search)戳杀,它類似于樹的先根遍歷(先訪問樹的根節(jié)點(diǎn))。其定義如下:
假設(shè)從圖中的某個頂點(diǎn)v出發(fā)夭苗,訪問此節(jié)點(diǎn)后信卡,然后依次從v的未被訪問的鄰接點(diǎn)出發(fā)深度優(yōu)先遍歷圖,直到圖中所有和v有路徑相通的頂點(diǎn)都被訪問到题造;若此時圖中尚有頂點(diǎn)未被訪問傍菇,則選另選一個未曾訪問的頂點(diǎn)作為起始點(diǎn)重復(fù)上述過程,直至圖中的所有節(jié)點(diǎn)都被訪問到為止界赔。

下面演示對示例圖的深度優(yōu)先遍歷:假設(shè)從起始點(diǎn)v1開始遍歷丢习,在訪問了v1后牵触,選擇其鄰接點(diǎn)v2。因為v2未曾訪問過泛领,則從v2出發(fā)進(jìn)行深度優(yōu)先遍歷荒吏。依次類推,接著從v4渊鞋、v8绰更、v5出發(fā)進(jìn)行遍歷。在訪問了v5后锡宋,由于v5的鄰接點(diǎn)都已被訪問儡湾,則遍歷回退到v8。同樣的理由执俩,繼續(xù)回退到v4徐钠、v2直至v1,此時v1的另一個鄰接點(diǎn)v3未被訪問役首,則遍歷又從v1到v3,再繼續(xù)進(jìn)行下去尝丐。于是得到節(jié)點(diǎn)的線性順序為:v1 -> v2 -> v4 -> v8 -> v5 -> v3 -> v6 -> v7,即示例圖中紅色箭頭線為其深度優(yōu)先遍歷順序衡奥。

深度優(yōu)先遍歷-PreOrder順序(紅色箭頭線及序號為遍歷順序)

算法分析

從上述的描述可以看出爹袁,深度優(yōu)先遍歷的定義就是一個遞歸的過程,每次都以當(dāng)前節(jié)點(diǎn)的第一個未曾訪問過的鄰接點(diǎn)進(jìn)行深度優(yōu)先遍歷的過程矮固。因此使用遞歸函數(shù)實現(xiàn)該算法是最直觀的實現(xiàn)方式失息,但由于遞歸的過程是函數(shù)棧累積的過程,如果節(jié)點(diǎn)數(shù)較多档址,容易造成函數(shù)棧的溢出而導(dǎo)致程序崩潰盹兢,因此正常生產(chǎn)環(huán)境一般會使用一個棧結(jié)構(gòu)(Stack)來存放遍歷的節(jié)點(diǎn)以模擬函數(shù)棧的調(diào)用情況,以此避免遞歸的缺陷守伸。

Guava的大體實現(xiàn)思路也是如此绎秒,使用棧結(jié)構(gòu)來替代函數(shù)的遞歸實現(xiàn)。另外含友,Guava對深度優(yōu)先遍歷還進(jìn)行了擴(kuò)展:區(qū)分節(jié)點(diǎn)的訪問順序是第一次訪問到節(jié)點(diǎn)的順序(PreOrder)替裆,還是訪問到底后回退時的節(jié)點(diǎn)順序(PostOrder)。

附上Guava的原文注釋:
"Pre-order" implies that nodes appear in the {@code Iterable} in the order in which they are first visited.
"Post-order" implies that nodes appear in the {@code Iterable} in the order in which they are visited for the last time.

因此窘问,對于深度優(yōu)先遍歷辆童,Guava根據(jù)這兩個場景定義了下面兩個接口:

//第一次訪問到節(jié)點(diǎn)的順序(Pre-order)
public abstract Iterable<N> depthFirstPreOrder(N startNode);

//訪問到最后,然后回退訪問節(jié)點(diǎn)的順序(Post-order)
public abstract Iterable<N> depthFirstPostOrder(N startNode);

它的實現(xiàn)與上述描述相同惠赫,也是創(chuàng)建了一個Iterable把鉴,然后實現(xiàn)它的iterator()接口,由于這兩個接口相近,下面僅舉例說明其中一個(depthFirstPostOrder):

@Override
public Iterable<N> depthFirstPostOrder(final N startNode) {
    /**
     * 創(chuàng)建一個匿名的Iterable庭砍,并實現(xiàn)iterator()接口
     */
    return new Iterable<N>() {
        @Override
        public Iterator<N> iterator() {
            /**
             * 返回一個自定義的Iterator
             */
            return new DepthFirstIterator(startNode, Order.POSTORDER);

            /**
             * depthFirstPreOrder()遍歷時也是創(chuàng)建了DepthFirstIterator()场晶,只是參數(shù)換成了Order.PREORDER
             */
            //return new DepthFirstIterator(startNode, Order.PREORDER);
        }
    };
}

該自定義迭代器DepthFirstIterator中,其主體實現(xiàn)在迭代函數(shù)next()中怠缸,每迭代一次(調(diào)用next()函數(shù)一次)返回一個以深度優(yōu)先遍歷為順序的一個節(jié)點(diǎn):


/**
 * 自定義深度優(yōu)先遍歷的結(jié)果迭代器(AbstractIterator繼承自Iterator)
 */
private final class DepthFirstIterator extends AbstractIterator<N> {

    /**
     * 模擬函數(shù)遞歸遍歷節(jié)點(diǎn)的堆棧诗轻,每次入棧的數(shù)據(jù)是:節(jié)點(diǎn)-節(jié)點(diǎn)的鄰節(jié)點(diǎn)集
     */
    private final Deque<NodeAndSuccessors> stack = new ArrayDeque<>();

    /**
     * 標(biāo)識節(jié)點(diǎn)是否已訪問過
     */
    private final Set<N> visited = new HashSet<>();

    /**
     * 指定深度優(yōu)先遍歷的順序: PREORDER, POSTORDER
     */
    private final Order order;

    /**
     * 構(gòu)造函數(shù),首先將指定的起始節(jié)點(diǎn)-及鄰接點(diǎn)入棧
     * @param root:起始節(jié)點(diǎn)
     * @param order:指定遍歷的順序
     */
    DepthFirstIterator(N root, Order order) {
        stack.push(withSuccessors(root)); //節(jié)點(diǎn)-及鄰接點(diǎn)入棧
        this.order = order;
    }

    /**
     * 基類封裝的next()函數(shù)
     * @return
     */
    @Override
    protected N computeNext() {
        while (true) { //直到找到合適遍歷順序的節(jié)點(diǎn)
            /**
             * 堆棧為空時揭北,遍歷結(jié)束扳炬,返回null
             */
            if (stack.isEmpty()) {
                return endOfData();
            }

            /**
             * 每次拿到棧頂?shù)臄?shù)據(jù)參與運(yùn)算,且由于withSuccessors()
             * 對應(yīng)的鄰接點(diǎn)也是Iterator,因此需要注意運(yùn)算中是否所有
             * 的鄰接點(diǎn)都有遍歷完畢。(算法核心)
             */
            NodeAndSuccessors node = stack.getFirst();

            /**
             * Set<>.add()操作返回true時搔体,表示為第一次加入到Set集合中
             * 對應(yīng)到遍歷順序 -- PREORDER恨樟。因此firstVisit標(biāo)識PREORDER順序的訪問
             */
            boolean firstVisit = visited.add(node.node);

            /**
             * 當(dāng)successorIterator都遍歷完畢時(!node.successorIterator.hasNext()),
             * 表示該路徑已經(jīng)遍歷完畢了,現(xiàn)在需要開始回退疚俱,則該節(jié)點(diǎn)是回退時的節(jié)點(diǎn)
             * 對應(yīng)到遍歷順序 -- POSTORDER劝术。因此lastVisit標(biāo)識POSTORDER順序的訪問
             *
             */
            boolean lastVisit = !node.successorIterator.hasNext();

            /**
             * 當(dāng)前步驟是否產(chǎn)生了一個可以返回的迭代節(jié)點(diǎn)(firstVisit | lastVisit)
             */
            boolean produceNode =
                (firstVisit && order == Order.PREORDER) || (lastVisit && order == Order.POSTORDER);

            /**
             * 如果當(dāng)前節(jié)點(diǎn)的鄰接點(diǎn)集都已遍歷完畢時,則將該節(jié)點(diǎn)出棧呆奕。
             * 以致下一次stack.getFirst()時獲得的數(shù)據(jù)變成未訪問的新數(shù)據(jù)
             */
            if (lastVisit) {
                stack.pop();
            } else {
                /**
                 * 如果鄰接點(diǎn)還沒遍歷完畢养晋,則每次將一個鄰接點(diǎn)及鄰接點(diǎn)的鄰接點(diǎn)集合壓入棧中
                 * 需注意該鄰接點(diǎn)是否已有訪問過
                 */
                N successor = node.successorIterator.next();
                if (!visited.contains(successor)) {
                    stack.push(withSuccessors(successor));
                }
            }
            /**
             * 若當(dāng)前節(jié)點(diǎn)滿足遍歷順序(firstVisit | lastVisit),則返回該節(jié)點(diǎn)。
             * 否則梁钾,將繼續(xù)上述棧中數(shù)據(jù)的操作匙握,直到找到滿足produceNode的節(jié)點(diǎn)或者棧中數(shù)據(jù)遍歷完畢
             */
            if (produceNode) {
                return node.node;
            }
        }
    }

    /**
     * 構(gòu)建堆棧數(shù)據(jù)結(jié)構(gòu):節(jié)點(diǎn)-及鄰接點(diǎn)
     * @param node
     * @return
     */
    NodeAndSuccessors withSuccessors(N node) {
        return new NodeAndSuccessors(node, graph.successors(node));
    }
}

針對上面給出的深度度優(yōu)先遍歷算法實現(xiàn),下面還是寫一個簡單demo驗證其實現(xiàn)結(jié)果:

//構(gòu)建示例圖所示的圖結(jié)構(gòu)(無向圖)
MutableGraph<String> graph = GraphBuilder.undirected()
    .nodeOrder(ElementOrder.<String>natural()).build();

graph.putEdge(V1, V2);
graph.putEdge(V1, V3);
graph.putEdge(V2, V4);
graph.putEdge(V2, V5);
graph.putEdge(V3, V6);
graph.putEdge(V3, V7);
graph.putEdge(V4, V8);
graph.putEdge(V5, V8);
graph.putEdge(V6, V7);

//測試depthFirstPreOrder()接口陈轿,從V1開始遍歷
Iterable<String> dfsPre = Traverser.forGraph(graph).depthFirstPreOrder(V1);
for (String node : iterable) {
    print(node);
}


//測試depthFirstPostOrder()接口,從V1開始遍歷
Iterable<String> dfsPost = Traverser.forGraph(graph).depthFirstPostOrder(V1);
for (String node : iterable) {
    print(node);
}

輸出順序為:

dfs pre-order graph: {V1,V3,V6,V7,V2,V5,V8,V4}
dfs post-order graph: {V7,V6,V3,V4,V8,V5,V2,V1}

注:該順序與示例圖的紅色箭頭線標(biāo)識的順序有所不同秦忿,但仍滿足深度優(yōu)先遍歷的順序麦射,其差異原因前面已經(jīng)有說明過。

另外灯谣,由于圖節(jié)點(diǎn)連接屬性的復(fù)雜性(任何兩個節(jié)點(diǎn)都可能存在連接)潜秋,所以在遍歷過程中需要另設(shè)一個Set<N>來記錄該節(jié)點(diǎn)的訪問標(biāo)記(因為圖中如果存在回路時會存在重復(fù)訪問的問題);為此胎许,Guava對于明確不存在回路的樹形圖(即有向無環(huán)圖)提供了另一種更優(yōu)的訪問方法forTree()峻呛,不過它也是在前面介紹的三種遍歷方法的基礎(chǔ)上進(jìn)行優(yōu)化實現(xiàn)的,具體可以參考原文的前面的鏈接進(jìn)行查看辜窑。

最后钩述,文中示例代碼的詳細(xì)實現(xiàn)參見:https://github.com/Jarrywell/GH-Demo/blob/master/app/src/main/java/com/android/test/demo/graph/TestTraverser.java

參考文檔

《數(shù)據(jù)結(jié)構(gòu)》-- 嚴(yán)蔚敏

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市穆碎,隨后出現(xiàn)的幾起案子牙勘,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件方面,死亡現(xiàn)場離奇詭異放钦,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)恭金,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進(jìn)店門操禀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人横腿,你說我怎么就攤上這事颓屑。” “怎么了蔑水?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵邢锯,是天一觀的道長。 經(jīng)常有香客問我搀别,道長丹擎,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任歇父,我火速辦了婚禮蒂培,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘榜苫。我一直安慰自己护戳,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布垂睬。 她就那樣靜靜地躺著媳荒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪驹饺。 梳的紋絲不亂的頭發(fā)上钳枕,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天,我揣著相機(jī)與錄音赏壹,去河邊找鬼鱼炒。 笑死,一個胖子當(dāng)著我的面吹牛蝌借,可吹牛的內(nèi)容都是我干的昔瞧。 我是一名探鬼主播,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼菩佑,長吁一口氣:“原來是場噩夢啊……” “哼自晰!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起擎鸠,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤缀磕,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體袜蚕,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡糟把,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了牲剃。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片遣疯。...
    茶點(diǎn)故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖凿傅,靈堂內(nèi)的尸體忽然破棺而出缠犀,到底是詐尸還是另有隱情,我是刑警寧澤聪舒,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布辨液,位于F島的核電站,受9級特大地震影響箱残,放射性物質(zhì)發(fā)生泄漏滔迈。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一被辑、第九天 我趴在偏房一處隱蔽的房頂上張望燎悍。 院中可真熱鬧,春花似錦盼理、人聲如沸谈山。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽奏路。三九已至,卻和暖如春臊诊,著一層夾襖步出監(jiān)牢的瞬間思劳,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工妨猩, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人秽褒。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓壶硅,卻偏偏與公主長得像,于是被迫代替她去往敵國和親销斟。 傳聞我的和親對象是個殘疾皇子庐椒,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評論 2 353

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