數(shù)據(jù)結(jié)構(gòu)與算法(四),樹

前面講到的順序表、棧和隊列都是一對一的線性結(jié)構(gòu),這節(jié)講一對多的線性結(jié)構(gòu)——樹。「一對多」就是指一個元素只能有一個前驅(qū),但可以有多個后繼。

目錄:

  • 一、基本概念
  • 二、樹的存儲結(jié)構(gòu)
  • 1、雙親表示法
  • 2、孩子表示法
  • 3、孩子兄弟表示法
  • 三、二叉樹
  • 1、基本概念
  • 2、二叉樹的性質(zhì)
  • 3、二叉樹的實現(xiàn)
  • 4、二叉樹的遍歷
  • 四、線索二叉樹
  • 五、樹、森林與二叉樹的轉(zhuǎn)換
  • 六、總結(jié)

一、基本概念

樹(tree)是n(n>=0)個結(jié)點的有窮集。n=0時稱為空樹。在任意一個非空樹中:(1)每個元素稱為結(jié)點(node);(2)僅有一個特定的結(jié)點被稱為根結(jié)點或樹根(root)。(3)當(dāng)n>1時,其余結(jié)點可分為m(m≥0)個互不相交的集合T1,T2,……Tm,其中每一個集合Ti(1<=i<=m)本身也是一棵樹,被稱作根的子樹(subtree)。

注意:

  • n>0時,根節(jié)點是唯一的。
  • m>0時,子樹的個數(shù)沒有限制,但它們一定是互不相交的。

結(jié)點擁有的子樹數(shù)被稱為結(jié)點的(Degree)。度為0的結(jié)點稱為葉節(jié)點(Leaf)或終端結(jié)點,度不為0的結(jié)點稱為分支結(jié)點。除根結(jié)點外,分支結(jié)點也被稱為內(nèi)部結(jié)點。結(jié)點的子樹的根稱為該結(jié)點的孩子(Child),該結(jié)點稱為孩子的雙親父結(jié)點。同一個雙親的孩子之間互稱為兄弟樹的度是樹中各個結(jié)點度的最大值。

結(jié)點的層次(Level)從根開始定義起,根為第一層,根的孩子為第二層。雙親在同一層的結(jié)點互為堂兄弟。樹中結(jié)點的最大層次稱為樹的深度(Depth)或高度。如果將樹中結(jié)點的各個子樹看成從左到右是有次序的,不能互換的,則稱該樹為有序樹,否則稱為無序樹森林是m(m>=0)棵互不相交的樹的集合。

樹的定義:

二、樹的存儲結(jié)構(gòu)

由于樹中每個結(jié)點的孩子可以有多個,所以簡單的順序存儲結(jié)構(gòu)無法滿足樹的實現(xiàn)要求。下面介紹三種常用的表示樹的方法:雙親表示法、孩子表示法和孩子兄弟表示法。

1、雙親表示法

由于樹中每個結(jié)點都僅有一個雙親結(jié)點(根節(jié)點沒有),我們可以使用指向雙親結(jié)點的指針來表示樹中結(jié)點的關(guān)系。這種表示法有點類似于前面介紹的靜態(tài)鏈表的表示方法。具體做法是以一組連續(xù)空間存儲樹的結(jié)點,同時在每個結(jié)點中,設(shè)一個「游標(biāo)」指向其雙親結(jié)點在數(shù)組中的位置。代碼如下:

public class PTree<E> {
    private static final int DEFAULT_CAPACITY = 100;
    private int size;
    private Node[] nodes;

    private class Node() {
        E data;
        int parent;

        Node(E data, int parent) {
            this.data = data;
            this.parent = parent;
        }
    }

    public PTree() {
        nodes = new PTree.Node[DEFAULT_CAPACITY];
    }
}

由于根結(jié)點沒有雙親結(jié)點,我們約定根節(jié)點的parent域值為-1。樹的雙親表示法如下所示:

雙親表示法

這樣的存儲結(jié)構(gòu),我們可以根據(jù)結(jié)點的parent域在O(1)的時間找到其雙親結(jié)點,但是只能通過遍歷整棵樹才能找到它的孩子結(jié)點。一種解決辦法是在結(jié)點結(jié)構(gòu)中增加其孩子結(jié)點的域,但若結(jié)點的孩子結(jié)點很多,結(jié)點結(jié)構(gòu)將會變的很復(fù)雜。

2、孩子表示法

由于樹中每個結(jié)點可能有多個孩子,可以考慮用多重鏈表,即每個結(jié)點有多個指針域,每個指針指向一個孩子結(jié)點,我們把這種方法叫多重鏈表表示法。它有兩種設(shè)計方案:

方案一:指針域的個數(shù)等于樹的度。其結(jié)點結(jié)構(gòu)可以表示為:

class Node() {
    E data;
    Node child1;
    Node child2;
    ...
    Node childn;
}

對于上一節(jié)中的樹,樹的度為3,其實現(xiàn)為:

方案一

顯然,當(dāng)樹中各結(jié)點的度相差很大時,這種方法對空間有很大的浪費(fèi)。

方案二,每個結(jié)點指針域的個數(shù)等于該結(jié)點的度,取一個位置來存儲結(jié)點指針的個數(shù)。其結(jié)點結(jié)構(gòu)可以表示為:

class Node() {
    E data;
    int degree;
    Node[] nodes;
    Node(int degree) {
        this.degree = degree;
        nodes = new Node[degree];
    }
}

對于上一節(jié)中的樹,這種方法的實現(xiàn)為:

方案二

這種方法克服了浪費(fèi)空間的缺點,但由于各結(jié)點結(jié)構(gòu)不同,在運(yùn)算上會帶來時間上的損耗。

為了減少空指針的浪費(fèi),同時又使結(jié)點相同。我們可以將順序存儲結(jié)構(gòu)和鏈?zhǔn)酱鎯Y(jié)構(gòu)相結(jié)合。具體做法是:把每個結(jié)點的孩子結(jié)點以單鏈表的形式鏈接起來,若是葉子結(jié)點則此單鏈表為空。然后將所有鏈表存放進(jìn)一個一維數(shù)組中。這種表示方法被稱為孩子表示法。其結(jié)構(gòu)為:

孩子表示法

代碼表示:

public class CTree<E> {
    private static final int DEFAULT_CAPACITY = 100;
    private int size;
    private Node[] nodes;

    private class Node() {
        E data;
        ChildNode firstChild;
    }
    
    //鏈表結(jié)點
    private class ChildNode() {
        int cur; //存放結(jié)點在nodes數(shù)組中的下標(biāo)
        ChildNode next;
    }

    public CTree() {
        nodes = new CTree.Node[DEFAULT_CAPACITY];
    }
}

這種結(jié)構(gòu)對于查找某個結(jié)點的孩子結(jié)點比較容易,但若想要查找它的雙親或兄弟,則需要遍歷整棵樹,比較麻煩。可以將雙親表示法和孩子表示法相結(jié)合,這種方法被稱為雙親孩子表示法。其結(jié)構(gòu)如下:

雙親孩子表示法

其代碼和孩子表示法的基本相同,只需在Node結(jié)點中增加parent域即可。

3、孩子兄弟表示法

任意一棵樹,它的結(jié)點的第一個孩子如果存在則是唯一的,它的右兄弟如果存在也是唯一的。因此,我們可以使用兩個分別指向該結(jié)點的第一個孩子和右兄弟的指針來表示一顆樹。其結(jié)點結(jié)構(gòu)為:

class Node() {
    E data;
    Node firstChild;
    Node rightSib;
}

其結(jié)構(gòu)如下:

孩子兄弟表示法

這個方法,可以方便的查找到某個結(jié)點的孩子,只需先通過firstChild找到它的第一個孩子,然后通過rightSib找到它的第二個孩子,接著一直下去,直到找到想要的孩子。若要查找某個結(jié)點的雙親和左兄弟,使用這個方法則比較麻煩。

這個方法最大的好處是將一顆復(fù)雜的樹變成了一顆二叉樹。這樣就可以使用二叉樹的一些特性和算法了。

三、二叉樹

1、基本概念

二叉樹(Binary Tree)是每個節(jié)點最多有兩個子樹的樹結(jié)構(gòu)。通常子樹被稱作“左子樹”(left subtree)和“右子樹”(right subtree)。

二叉樹的特點:

  • 二叉樹不存在度大于2的結(jié)點。
  • 二叉樹的子樹有左右之分,次序不能顛倒。

如下圖中,樹1和樹2是同一棵樹,但它們是不同的二叉樹。

二叉樹

1)、斜樹

所有的結(jié)點都只有左子樹的二叉樹叫左斜樹。所有的結(jié)點都只有右子樹的二叉樹叫右斜樹。這兩者統(tǒng)稱為斜樹。

斜樹每一層只有一個結(jié)點,結(jié)點的個數(shù)與二叉樹的深度相同。其實斜樹就是線性表結(jié)構(gòu)。

2)、滿二叉樹

在一棵二叉樹中,如果所有分支結(jié)點都存在左子樹和右子樹,并且所有葉子都在同一層上,這樣的二叉樹稱為滿二叉樹。

滿二叉樹具有如下特點:

  • 葉子只能出現(xiàn)在最下一層
  • 非葉子結(jié)點的度一定是2
  • 同樣深度的二叉樹中,滿二叉樹的結(jié)點個數(shù)最多,葉子數(shù)最多。

3)、完全二叉樹

若設(shè)二叉樹的高度為h,除第 h 層外,其它各層 (1~h-1) 的結(jié)點數(shù)都達(dá)到最大個數(shù),第h層有葉子結(jié)點,并且葉子結(jié)點都是從左到右依次排布,這就是完全二叉樹。

完全二叉樹的特點:

  • 葉子結(jié)點只能出現(xiàn)在最下兩層
  • 最下層葉子在左部并且連續(xù)
  • 同樣結(jié)點數(shù)的二叉樹,完全二叉樹的深度最小

4)、平衡二叉樹

平衡二叉樹又被稱為AVL樹(區(qū)別于AVL算法),它是一棵二叉排序樹,且具有以下性質(zhì):它是一棵空樹或它的左右兩個子樹的高度差的絕對值不超過1,并且左右兩個子樹都是一棵平衡二叉樹

2、二叉樹的性質(zhì)

  1. 在二叉樹的第i層上至多有2i-1個結(jié)點(i>=1)。

  2. 深度為k的二叉樹至多有2k-1個結(jié)點(k>=1)。

  3. 對任何一棵二叉樹T,如果其終端結(jié)點個數(shù)為n0,度為2的結(jié)點數(shù)為n2,則n0 = n2 + 1。

  4. 具有n個結(jié)點的完全二叉樹的深度為「log2n」+ 1(「x」表示不大于x的最大整數(shù))。

  5. 如果對一棵有n個結(jié)點的完全二叉樹的結(jié)點按層序編號(從第一層到第「log2n」+ 1層,每層從左到右),對任一結(jié)點i(1≤i≤n)有:

    • 若i=1,則結(jié)點i是二叉樹的根,無雙親;如i>1,則其雙親是結(jié)點「i/2」。
    • 如2i>n,則結(jié)點i無左孩子(結(jié)點i為葉子結(jié)點);否則其左孩子是結(jié)點2i。
    • 若2i+1>n,則結(jié)點i無右孩子;否則其右孩子是結(jié)點2i+1。

3、二叉樹的實現(xiàn)

二叉樹是一種特殊的樹,它的存儲結(jié)構(gòu)相對于前面談到的一般樹的存儲結(jié)構(gòu)要簡單一些。

1)、順序存儲

二叉樹的順序存儲結(jié)構(gòu)就是用一維數(shù)組來存儲二叉樹中的結(jié)點。不使用數(shù)組的第一個位置。結(jié)點的存儲位置反映了它們之間的邏輯關(guān)系:位置k的結(jié)點的雙親結(jié)點的位置為「k/2」,它的兩個孩子結(jié)點的位置分別為2k和2k+1。

代碼實現(xiàn):


public class ArrayBinaryTree<E> {

    private static final int DEFAULT_DEPTH = 5;

    private int size = 0;
    private E[] datas;

    ArrayBinaryTree() {
        this(DEFAULT_DEPTH);
    }

    @SuppressWarnings("unchecked")
    ArrayBinaryTree(int depth) {
        datas = (E[]) new Object[(int)Math.pow(2, depth)];
    }

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

    public int size(){ return size; }

    public E getRoot() { return datas[1]; }

    // 返回指定節(jié)點的父節(jié)點    
    public E getParent(int index) {  
        checkIndex(index);  
        if (index == 1) {    
            throw new RuntimeException("根節(jié)點不存在父節(jié)點!");    
        }    
        return datas[index/2];    
    }    
        
    //獲取右子節(jié)點    
    public E getRight(int index){    
        checkIndex(index*2 + 1);  
        return datas[index * 2 + 1];    
    }    
        
    //獲取左子節(jié)點    
    public E getLeft(int index){    
        checkIndex(index*2);    
        return datas[index * 2];    
    }     
        
    //返回指定數(shù)據(jù)的位置    
    public int indexOf(E data){    
       if(data==null){   
         throw new NullPointerException();  
       } else {  
           for(int i=0;i<datas.length;i++) {  
               if(data.equals(datas[i])) {  
                   return i;  
               }  
           }  
       }  
        return -1;    
    }

    //順序添加元素
    public void add(E element) {
        checkIndex(size + 1);
        datas[size + 1] = element;
        size++;
    }

    //在指定位置添加元素
    public void add(E element, int parent, boolean isLeft) {

        if(datas[parent] == null) {  
            throw new RuntimeException("index["+parent+"] is not Exist!");  
        }  
        if(element == null) {  
            throw new NullPointerException();  
        } 

        if(isLeft) {
            checkIndex(2*parent);
            if(datas[parent*2] != null) {  
                throw new RuntimeException("index["+parent*2+"] is  Exist!");  
            }
            datas[2*parent] = element;
        }else {
            checkIndex(2*parent + 1);
            if(datas[(parent+1)*2]!=null) {  
                throw new RuntimeException("index["+ parent*2+1 +"] is  Exist!");  
            } 
            datas[2*parent + 1] = element;
        }
        size++;
    }

    //檢查下標(biāo)是否越界
    private void checkIndex(int index) {  
        if(index <= 0 || index >= datas.length) {  
            throw new IndexOutOfBoundsException();  
        }  
    } 
    public static void main(String[] args) {
        char[] data = {'A','B','C','D','E','F','G','H','I','J'};
        ArrayBinaryTree<Character> abt = new ArrayBinaryTree<>();
        for(int i=0; i<data.length; i++) {
            abt.add(data[i]);
        }
        System.out.print(abt.getParent(abt.indexOf('J')));
    }
}

一棵深度為k的右斜樹,只有k個結(jié)點,但卻需要分配2k-1個順序存儲空間。所以順序存儲結(jié)構(gòu)一般只用于完全二叉樹。

2)、鏈?zhǔn)酱鎯?/strong>

二叉樹每個結(jié)點最多有兩個孩子,所以為它設(shè)計一個數(shù)據(jù)域和兩個指針域即可。我們稱這樣的鏈表為二叉鏈表。其結(jié)構(gòu)如下圖:

代碼如下:

import java.util.*;
public class LinkedBinaryTree<E> {    
    private List<Node> nodeList = null;  
   
    private class Node {  
        Node leftChild;  
        Node rightChild;  
        E data;  
  
        Node(E data) {  
            this.data = data;  
        }  
    }  
  
    public Node getRoot() {
        return nodeList.get(0);
    }

    public void createBinTree(E[] array) {  
        nodeList = new LinkedList<Node>();  

        for (int i = 0; i < array.length; i++) {  
            nodeList.add(new Node(array[i]));  
        }  
        // 對前l(fā)asti-1個父節(jié)點按照父節(jié)點與孩子節(jié)點的數(shù)字關(guān)系建立二叉樹  
        for (int i = 0; i < array.length / 2 - 1; i++) {   
            nodeList.get(i).leftChild = nodeList.get(i * 2 + 1);    
            nodeList.get(i).rightChild = nodeList.get(i * 2 + 2);  
        }  
        // 最后一個父節(jié)點:因為最后一個父節(jié)點可能沒有右孩子,所以單獨拿出來處理  
        int lastParent = array.length / 2 - 1;  
        nodeList.get(lastParent).leftChild = nodeList  .get(lastParent * 2 + 1);  

        // 右孩子,如果數(shù)組的長度為奇數(shù)才建立右孩子  
        if (array.length % 2 == 1) {  
            nodeList.get(lastParent).rightChild = nodeList.get(lastParent * 2 + 2);  
        }  
    } 

    public static void main(String[] args) {
        Character[] data = {'A','B','C','D','E','F','G','H','I','J'};
        LinkedBinaryTree<Character> ldt = new LinkedBinaryTree<>();
        ldt.createBinTree(data);
    }
}

4、二叉樹的遍歷

二叉樹的遍歷(traversing binary tree)是指從根結(jié)點出發(fā),按照某種次序依次訪問二叉樹中所有結(jié)點,使得每個結(jié)點被訪問一次且僅被訪問一次。

二叉樹的遍歷主要有四種:前序遍歷、中序遍歷、后序遍歷和層序遍歷。

1)、前序遍歷

先訪問根結(jié)點,然后遍歷左子樹,最后遍歷右子樹。

代碼:

//順序存儲
public void preOrderTraverse(int index) {  
    if (datas[index] == null)  
        return;  
    System.out.print(datas[index] + " ");  
    preOrderTraverse(index*2);  
    preOrderTraverse(index*2+1);  
} 

//鏈?zhǔn)酱鎯? public void preOrderTraverse(Node node) {  
    if (node == null)  
        return;  
    System.out.print(node.data + " ");  
    preOrderTraverse(node.leftChild);  
    preOrderTraverse(node.rightChild);  
} 

2)、中序遍歷

先遍歷左子樹,然后遍歷根結(jié)點,最后遍歷右子樹。

//鏈?zhǔn)酱鎯? public void inOrderTraverse(Node node) {  
    if (node == null)  
        return;  
    inOrderTraverse(node.leftChild);
    System.out.print(node.data + " ");  
    inOrderTraverse(node.rightChild);  
} 

3)、后序遍歷

先遍歷左子樹,然后遍歷右子樹,最后遍歷根結(jié)點。

//鏈?zhǔn)酱鎯? public void postOrderTraverse(Node node) {  
    if (node == null)  
        return;  
    postOrderTraverse(node.leftChild);
    postOrderTraverse(node.rightChild);  
    System.out.print(node.data + " ");  
} 

4)、層序遍歷

從上到下逐層遍歷,在同一層中,按從左到右的順序遍歷。如上一節(jié)中的二叉樹層序遍歷的結(jié)果為ABCDEFGHIJ。

注意:

  • 已知前序遍歷和中序遍歷,可以唯一確定一棵二叉樹。
  • 已知后序遍歷和中序遍歷,可以唯一確定一棵二叉樹。
  • 已知前序遍歷和后序遍歷,不能確定一棵二叉樹。

如前序遍歷是ABC,后序遍歷是CBA的二叉樹有:

四、線索二叉樹

對于n個結(jié)點的二叉樹,在二叉鏈存儲結(jié)構(gòu)中有n+1個空指針域,利用這些空指針域存放在某種遍歷次序下該結(jié)點的前驅(qū)結(jié)點和后繼結(jié)點的指針,這些指針被稱為線索,加上線索的二叉樹稱為線索二叉樹。

結(jié)點結(jié)構(gòu)如下:

線索二叉樹的結(jié)點結(jié)構(gòu)

其中:

  • lTag為0時,lChild指向該結(jié)點的左孩子,為1時指向該結(jié)點的前驅(qū)
  • rTag為0時,rChild指向該結(jié)點的右孩子,為1時指向該結(jié)點的后繼。

線索二叉樹的結(jié)構(gòu)圖為:圖中藍(lán)色虛線為前驅(qū),紅色虛線為后繼

線索二叉樹

代碼如下:


public class ThreadedBinaryTree<E> {
    private TBTreeNode root;
    private int size;          // 大小  
    private TBTreeNode pre;   // 線索化的時候保存前驅(qū)  

    class TBTreeNode {
        E element;
        boolean lTag; //false表示指向孩子結(jié)點,true表示指向前驅(qū)或后繼的線索
        boolean rTag;
        TBTreeNode lChild;
        TBTreeNode rChild;

        public TBTreeNode(E element) {
            this.element = element;
        }
    }
    
    public ThreadedBinaryTree(E[] data) {
        this.pre = null;  
        this.size = data.length;  
        this.root = createTBTree(data, 1);
    }

    //構(gòu)建二叉樹
    public TBTreeNode createTBTree(E[] data, int index) {  
        if (index > data.length){  
            return null;  
        }  
        TBTreeNode node = new TBTreeNode(data[index - 1]);  
        TBTreeNode left = createTBTree(data, 2 * index);  
        TBTreeNode right = createTBTree(data, 2 * index + 1);  
        node.lChild = left;  
        node.rChild = right;  
        return node;  
    } 

    /** 
     * 將二叉樹線索化   
     */  
    public void inThreading(TBTreeNode node) {  
        if (node != null) {  
            inThreading(node.lChild);     // 線索化左孩子 

            if (node.lChild == null) {  // 左孩子為空  
                node.lTag = true;    // 將左孩子設(shè)置為線索  
                node.lChild = pre;  
            }  
            if (pre != null && pre.rChild == null) {  // 右孩子為空  
                pre.rTag = true;  
                pre.rChild = node;  
            }  
            pre = node;  

            inThreading(node.rChild);  // 線索化右孩子  
        }  
    }  
  
    /** 
     * 中序遍歷線索二叉樹 
     */  
    public void inOrderTraverseWithThread(TBTreeNode node) {

        while(node != null) {
            while(!node.lTag) { //找到中序遍歷的第一個結(jié)點
                node = node.lChild;
            }
            System.out.print(node.element + " "); 
            while(node.rTag && node.rChild != null) { //若rTag為true,則打印后繼結(jié)點
                node = node.rChild;
                System.out.print(node.element + " "); 
            }
            node = node.rChild;
        }
    }  
  
    /** 
     * 中序遍歷,線索化后不能使用
     */  
    public void inOrderTraverse(TBTreeNode node) {  
        if(node == null)
            return;
        inOrderTraverse(node.lChild);  
        System.out.print(node.element + " ");  
        inOrderTraverse(node.rChild);  
    } 

    public TBTreeNode getRoot() { return root;}

    public static void main(String[] args) {
        Character[] data = {'A','B','C','D','E','F','G','H','I','J'};
        ThreadedBinaryTree<Character> tbt = new ThreadedBinaryTree<>(data);
        tbt.inOrderTraverse(tbt.getRoot());
        System.out.println();
        tbt.inThreading(tbt.getRoot());
        tbt.inOrderTraverseWithThread(tbt.getRoot());
    }
}

線索二叉樹充分利用了空指針域的空間,提高了遍歷二叉樹的效率。

五、樹、森林與二叉樹的轉(zhuǎn)換

具體內(nèi)容請參考這篇博客 樹、森林與二叉樹的轉(zhuǎn)換,這里就不寫了。

六、總結(jié)

至此樹的知識算是基本總結(jié)玩完了,這一節(jié)開頭講了樹的一些基本概念,重點介紹了樹的三種不同的存儲方法:雙親表示法、孩子表示法和孩子兄弟表示法。由兄弟表示法引入了一種特殊的樹:二叉樹,并詳細(xì)介紹了它的性質(zhì)、不同結(jié)構(gòu)的實現(xiàn)方法和遍歷方法。最后介紹了線索二叉樹的實現(xiàn)方法(感覺這個最難理解)。

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

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