樹,二叉樹,二叉查找樹以及紅黑樹

本文主講二叉樹系列

樹的概念

鏈表通常可以提供比數組更大的靈活性,但由于鏈表是線性結構,所以很難使用它們來組織對象的分層表示。雖然隊列反映了某些層次,但它們是一維的,為了避免這種限制,創建了一個新型數據類型,稱為樹,樹由節點和弧組成。
樹在計算機科學里,是一種十分基礎的數據結構。幾乎所有操作系統都將文件存放在樹狀結構里;幾乎所有的編譯器都需要實現一個表達式樹;文件壓縮所用的哈夫曼算法需要用到哈夫曼樹;數據庫所使用的B樹,以及map,set等容器底層數據結構紅黑樹。


圖1.png

圖 1(A) 是使用樹結構存儲的集合 {A,B,C,D,E,F,G,H,I,J,K,L,M} 的示意圖。對于數據 A 來說,和數據 B、C、D 有關系;對于數據 B 來說,和 E、F 有關系。這就是“一對多”的關系。

將具有“一對多”關系的集合中的數據元素按照圖 1(A)的形式進行存儲,整個存儲形狀在邏輯結構上看,類似于實際生活中倒著的樹(圖 1(B)倒過來),所以稱這種存儲結構為“樹型”存儲結構。

什么是二叉樹

簡單地理解,滿足以下兩個條件的樹就是二叉樹:
1.本身是有序樹;
2.樹中包含的各個節點的度不能超過 2,即只能是 0、1 或者 2;


圖2.gif

經過前人的總結,二叉樹具有以下幾個性質:
1.二叉樹中,第 i 層最多有 2^(i-1) 個結點。
2.如果二叉樹的深度為 K,那么此二叉樹最多有 2^K-1 個結點。
3.二叉樹中,終端結點數(葉子結點數)為 n0,度為 2 的結點數為 n2,則 n0=n2+1。

性質 3 的計算方法為:對于一個二叉樹來說,除了度為 0 的葉子結點和度為 2 的結點,剩下的就是度為 1 的結點(設為 n1),那么總結點 n=n0+n1+n2。
同時,對于每一個結點來說都是由其父結點分支表示的,假設樹中分枝數為 B,那么總結點數 n=B+1。而分枝數是可以通過 n1 和 n2 表示的,即 B=n1+2 * n2。所以,n 用另外一種方式表示為 n=n1+2 * n2+1。
兩種方式得到的 n 值組成一個方程組,就可以得出 n0=n2+1。
二叉樹還可以繼續分類,衍生出滿二叉樹和完全二叉樹

滿二叉樹

如果二叉樹中除了葉子結點,每個結點的度都為 2,則此二叉樹稱為滿二叉樹。


滿二叉樹.gif

如圖所示就是一棵滿二叉樹。

滿二叉樹除了滿足普通二叉樹的性質,還具有以下性質:
1.)滿二叉樹中第 i 層的節點數為 2^(n-1) 個。
2.)深度為 k 的滿二叉樹必有 2^k-1 個節點 ,葉子數為 2^(k-1)。
3.)滿二叉樹中不存在度為 1 的節點,每一個分支點中都兩棵深度相同的子樹,且葉子節點都在最底層。
4.)具有 n 個節點的滿二叉樹的深度為 log2(n+1)。

完全二叉樹

如果二叉樹中除去最后一層節點為滿二叉樹,且最后一層的結點依次從左到右分布,則此二叉樹被稱為完全二叉樹。


完全二叉樹.gif

如圖 a) 所示是一棵完全二叉樹,圖 b) 由于最后一層的節點沒有按照從左向右分布,因此只能算作是普通的二叉樹。

對于任意一個完全二叉樹來說,如果將含有的結點按照層次從左到右依次標號(如圖 3a))(從1開始標號,即根節點標號為1,若從0開始標號,則結論有所不同),對于任意一個結點 i ,完全二叉樹還有以下幾個結論成立:
1)當 i>0 時,父親結點為結點 [i/2] 。(i=1 時,表示的是根結點,無父親結點)
2)如果 2i>n(總結點的個數) ,則結點 i 肯定沒有左孩子(為葉子結點);否則其左孩子是結點 2i 。
3)如果 2i+1>n ,則結點 i 肯定沒有右孩子;否則右孩子是結點 2i+1 。

二叉樹的遍歷

在這里給出二叉樹的非遞歸遍歷算法
以下圖為例:



可在leetcode的探索中去刷關于樹的遍歷的題。(以下給出遍歷代碼皆在LeetCode中通過)

前序遍歷

前序遍歷結果:[1,2,4,5,3,6,7]
用棧來存儲節點:
1)節點1,2,4依次入棧,節點4 的左孩子為空,停止入棧;[1,2,4]
2)節點4 出棧,其右孩子為空,則不操作 接著節點2出棧,其右孩子不為空則進棧,節點5進棧,其左孩子為空停止進棧;[1,5]
3)節點5 出棧,右孩子為空;接著節點1(根節點)出棧,右孩子不為空則節點3進棧,節點6為其左孩子則繼續進棧;[3,6]
4)節點6,3出棧,節點7進棧;[7]
5)最后節點7出棧,左右孩子皆為空,棧也為空,則遍歷結束。
具體代碼如下:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> result;
        if(root==nullptr) return result;
        stack<TreeNode*> s;
        TreeNode* p=root;
        while(!s.empty() || p!=nullptr) //當回到根節點,判斷右節點,此時 棧是空的,因此加一個p不為空的判斷n
        {
            while(p!=nullptr)
            {
                //前序遍歷,先保存遍歷結果
                result.push_back(p->val);
                s.push(p);
                p=p->left;
            }
            TreeNode* t=s.top();
            s.pop();
            if(t->right!=nullptr)
            {
                p=t->right;
            }
        }
        return result;
    }
};
中序遍歷

中序遍歷結果:[4,2,5,1,6,3,7]
步驟和前序遍歷相同,不在贅述,只是打印結果位置不同,中序遍歷在節點出棧時打印結果,前序遍歷在節點進棧時打印結果。
具體代碼如下:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:

    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> result;
        if(root==nullptr)
        {
            return result;
        }
        TreeNode* p=root;
        stack<TreeNode*> s;
        while(!s.empty() || p!=nullptr)
        {
            while(p!=nullptr)
            {
                s.push(p);
                p=p->left;
            }
            TreeNode* t=s.top();
            s.pop();
            //出棧時打印結果
            result.push_back(t->val);
            if(t->right!=nullptr) p=t->right;
        }
        return result;
    }
};
后序遍歷

后序遍歷結果:[4,5,2,6,7,3,1]
過程稍微有些不同,類似于中序遍歷,左邊的節點都是要第一個進行訪問,在這里只是右節點和根節點的區別,同樣采用一個棧來解決這個問題。
只是后序遍歷在決定是否可以輸出當前節點的值的時候,需要考慮其左右子樹是否都已經遍歷完成。
所以需要設置一個lastVisit游標。
若lastVisit等于當前考查節點的右子樹,表示該節點的左右子樹都已經遍歷完成,則可以輸出當前節點。
并把lastVisit節點設置成當前節點,將當前游標節點node設置為空,下一輪就可以訪問棧頂元素
具體代碼:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> result;
        if(root==nullptr)
        {
            return result;
        }
        TreeNode* p=root;
        TreeNode* lastvisit=nullptr;
        stack<TreeNode*> s;
        while(!s.empty() || p!=nullptr)
        {
            while(p!=nullptr)
            {
                s.push(p);
                p=p->left;
            }
            TreeNode* t=s.top();
            if(t->right!=nullptr && lastvisit!=t->right)
            {
                p=t->right;
            }
            else
            {
                result.push_back(t->val);
                lastvisit=t;
                s.pop();
            }
        }
        return result;
    }
};
層序遍歷

層序遍歷結果:[1,2,3,4,5,6,7]
層序遍歷相對簡單,用一個隊列來完成,首先根節點入隊列,當節點處隊列的時候判斷左右子節點是否為空,不為空則依次入隊列,直到隊列為空則遍歷完畢。

class Solution {
    public:
        vector<int> levelOrder(TreeNode* root) 
        {
            vector<int> ret;
            queue<TreeNode*> q;
            if(root)
                q.push(root);
            while(!q.empty())
            {
                TreeNode* temp = q.front();
                ret.push_back(temp->val);
                q.pop();
                if(temp->left)
                    q.push(temp->left);
                if(temp->right)
                    q.push(temp->right);
            }
            return ret;
        }
    };

如果層序遍歷按層打印結果:[ [1],[2,3],[4,5,6,7] ],則需要記錄每層節點的數量,具體代碼如下:

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>> result;
        if(root==nullptr) return result;
        vector<int> t;
        queue<TreeNode*> q;
        TreeNode* p=root;
        int levelcount=1;
        int nextlevelcount=0;//記錄每層的節點數目
        q.push(root);
        while(!q.empty())
        {
            TreeNode* tmp=q.front();
            t.push_back(tmp->val);
            levelcount--;
            q.pop();
            if(tmp->left!=nullptr) 
            {
                q.push(tmp->left);
                nextlevelcount++;
            }
            if(tmp->right!=nullptr) 
            {
                q.push(tmp->right);
                nextlevelcount++;
            }
            if(levelcount==0)
            {
                result.push_back(t);
                t.clear();
                levelcount=nextlevelcount;
                nextlevelcount=0;
            }
        }
        return result;
    }
};

二叉搜索樹

完整代碼:https://github.com/songqiyuan/DataStruct/blob/master/Tree/searchTree.hpp
二叉搜索樹(BST)是二叉樹的一種特殊表示形式,它滿足如下特性:

1.每個節點中的值必須大于(或等于)存儲在其左側子樹中的任何值。
2每個節點中的值必須小于(或等于)存儲在其右子樹中的任何值。


二叉搜索樹.png

二叉排序樹的操作主要有:
1.查找:遞歸查找是否存在key。
2.插入:原樹中不存在key,插入key返回true,否則返回false。
3.構造:循環的插入操作。
4.刪除:(1)葉子節點:直接刪除,不影響原樹。
(2)僅僅有左或右子樹的節點:節點刪除后,將它的左子樹或右子樹整個移動到刪除節點的位置就可以,子承父業。
(3)既有左又有右子樹的節點:找到須要刪除的節點p的直接前驅或者直接后繼s(尋找前驅和后繼節點),用s來替換節點p,然后再刪除節點s

具體實現代碼如下:

    void  _deleteNode(int y)
    {
        TreeNode* node = findKey(y);
        if (node == nullptr) return ;
    
        if (node->right == nullptr)
        {
            TransPlant( node, node->left); //情況1,情況2
        }
        else if (node->left == nullptr)
        {
                        //輔助函數
            TransPlant( node, node->right);//情況1,情況2
        }
        else
        {
                        //情況3
            //查找后繼節點
            TreeNode* tmp=increment(node);
            if (tmp->parent != node)// && tmp->right!=nullptr)
            {
                TransPlant(tmp, tmp->right);
                tmp->right = node->right;
                tmp->right->parent = tmp;
            }
            TransPlant(node, tmp);
            tmp->left = node->left;
            tmp->left->parent = tmp;
        }
        resetNode(node);
    }

紅黑樹

完整代碼:https://github.com/songqiyuan/DataStruct/blob/master/Tree/rbtree.hpp
在學習紅黑樹前先理解旋轉的概念:
也許因為輸入的值不夠隨機,也許因為經過某些插入刪除操作二叉搜索樹可能會失去平衡,搜索效率降低,如圖:

平衡與不平衡.png

所謂平衡與否,并沒有一個絕對的測量標準,“平衡”的大致意義是:沒有任何一個節點深度過大,不同的平衡條件造就出不同的效率表現以及不同的實現復雜度。AVL-tree,RB-tree均可實現出平衡二叉樹。
由于刪除和添加操作對樹做了修改,結果可能違反了紅黑樹的性質,為了維護這些性質,必須要修改樹中某些節點的顏色以及指針結構。
指針結構的修改是通過旋轉來完成的,這是能保持二叉搜索樹性質的搜索樹局部操作。如圖的左旋和右旋:
左右旋轉.png

當在某個節點x上做左旋時,假設它的右孩子為y而不是NULL(x可以為其右孩子節點不是NULL節點的樹內任意節點),左旋以x到y的鏈為“支軸”進行。它使y成為樹的新的根節點,x成為y的左孩子,y的左孩子成為x的右孩子,右旋轉類似,具體代碼如下:

//左旋和右旋
//左旋
void RotateLeft(TreeNode* node)
{
    TreeNode* y = node->right;
    node->right = y->left;
    if (y->left != nullptr)
    {
        y->left->parent = node;
    }
    y->parent = node->parent;
    if (node->parent == nullptr)
    {
        _root = y;
    }
    else if (node == node->parent->left)
    {
        node->parent->left = y;
    }
    else if (node == node->parent->right)
    {
        node->parent->right = y;
    }
    y->left = node;
    node->parent = y;
}
//右旋
void RotateRight(TreeNode* node)
{
    TreeNode* y = node->left;
    node->left = y->right;
    if (y->right != nullptr)
    {
        y->right->parent = node;
    }
    y->parent = node->parent;
    if (node->parent == nullptr)
    {
        _root = y;
    }
    else if (node == node->parent->left)
    {
        node->parent->left = y;
    }
    else if (node == node->parent->right)
    {
        node->parent->right = y;
    }
    y->right = node;
    node->parent = y;
}
紅黑樹的性質

1.每個節點是紅色或者黑色;
2.根節點是黑色的;
3.每個葉節點是黑色的;
4.如果一個節點是紅色的,則它的兩個子節點都是黑色的;
5.對于每個節點,從該節點到其所有后代葉節點的簡單路徑上,均包含相同數目的黑節點。

下面主要講講可能破壞紅黑樹性質的刪除和插入操作。

紅黑樹的插入操作

紅黑樹的插入操作和二叉搜索樹的插入過程是類似的,只是插入的最后有所改變:將插入的節點著為紅色(為什么是紅色,因為要保持紅黑樹的性質5,如果插入的顏色為黑色,則必然改變某條路徑上的黑節點的個數,破壞紅黑樹的性質),然后調整紅黑樹(插入的節點為紅色,可能違背性質1或4)。

為了理解RBInsertFixup()函數是如何工作的,首先,要確定當節點被插入并著色后,紅黑樹的性質哪些是被破壞的。當插入為根節點時性質2(根節點為黑色)被破壞,當插入節點的父節點為紅色是性質4被破壞。下圖是一個修正過程:


插入節點.png

情況1:z的叔父節點y是紅色的(由性質4知,祖父節點為黑色)
情況2:z的叔父節點y是黑色的且z是一個右孩子
情況3:z的叔父節點y是黑色的且z是一個左孩子
代碼如下:

//插入操作
void _Insert(int key)
{
    TreeNode* node = _root;
    TreeNode* pNode = nullptr;
    while (node != nullptr)
    {
        pNode = node;
        if (key > node->val)
        {
            node = node->right;
        }
        else
        {
            node = node->left;
        }
    }
    TreeNode* pInsertNode = new TreeNode(key);
    //根節點為空,此時無數據
    if (pNode == nullptr)
    {
        _root = pInsertNode;
    }
    else if (pNode->val < key)
    {
        pNode->right = pInsertNode;
        pInsertNode->parent = pNode;
    }
    else
    {
        pNode->left = pInsertNode;
        pInsertNode->parent = pNode;
    }
        //修正紅黑樹
    RBInsertFixup(pInsertNode);
}
    //修正插入紅黑性質
void RBInsertFixup(TreeNode* node)
{
    //當前插入節點 ,其父節點為紅色
    while (node->parent != nullptr && node->parent->color == RED)
    {
        if (node->parent->parent->left == node->parent)
        {
            TreeNode* uncle = node->parent->parent->right;
            if (uncle!=nullptr && uncle->color == RED)
            {
                //若父節點為紅色或叔父節點為紅色,則祖父節點一定為黑色
                node->parent->color = BLACK;
                uncle->color = BLACK;
                node->parent->parent->color = RED;
                node = node->parent->parent;
            }
            else
            {
                if (node->parent->right == node)
                {
                    node = node->parent;
                    RotateLeft(node);
                }
                node->parent->color = BLACK;
                node->parent->parent->color = RED;
                RotateRight(node->parent->parent);
            }
        }
        else if(node->parent->parent->right==node->parent)
        {
            TreeNode* uncle = node->parent->parent->left;
            if (uncle != nullptr && uncle->color == RED)
            {
                node->parent->color = BLACK;
                uncle->color = BLACK;
                node->parent->parent->color = RED;
                node = node->parent->parent;
            }
            else 
            {
                if (node == node->parent->left)
                {
                    node = node->parent;
                    RotateRight(node);
                }
                node->parent->color = BLACK;
                node->parent->parent->color = RED;
                RotateLeft(node->parent->parent);
            }
        }
    }
    _root->color = BLACK;
}
紅黑樹的刪除
刪除修正.png

下面所有情況都在真實刪除的節點的顏色為黑的前提下,刪除節點顏色為紅色并不違反紅黑樹性質。
情況1:x的兄弟節點w是紅色的
由于刪除節點為黑色,而w為紅色,則w必定有黑色子節點(性質4),所以可以改變w和x.p的顏色,并做一次左旋(如圖中的(a)),而不違反紅黑樹的任何性質。旋轉后x的新兄弟節點為w的一個子節點,其顏色為黑色。這樣就將情況轉換為2,3或4處理。

情況2:x的兄弟節點w是黑色的,而且w的兩個子節點都是黑色的
如圖中(b)所示,將兄弟節點w著色為紅色,這樣以父節點為根的子樹到葉節點的黑色節點是相同的,但比其他路徑少一個黑色節點,因此將父節點設置為為新的x節點。此時父節點的顏色未知紅色或黑色,當為紅色時可以看出違背了性質4,不過此時我們只需將當前x節點設置為黑色就可以了,此時紅黑樹性質全部恢復。當為黑色節點時,轉換為其他情況。

情況3:x的兄弟節點w是黑色,w的左孩子是紅色,w的右孩子是黑色
如圖中(c),可以交換w和其左孩子的顏色,然后對w進行右旋而不違反紅黑樹的性質(不理解的話可以畫圖驗證一下),現在x新的兄弟節點,w的左孩子。這樣我們就把情況3轉換為了情況4.

情況4:x的兄弟節點w是黑色的,且w的右孩子是紅色的
如圖中(d)所示,想辦法用紅色節點來來填補左子樹(x.p.left)中減少的黑節點數,通過改變一些節點的顏色來實現:a.將w著色為其父節點的顏色;b.將w.right由紅色改為黑色;c.將x.p的顏色改為黑色(用該節點來填補左子樹中缺失的黑色節點數);d.將x.p節點左旋,到此紅黑樹性質恢復,將x設置為根節點,結束修正。

代碼實現:

    void  _deleteNode(int y)
    {
        TreeNode* node = findKey(y);
        if (node == nullptr) return;
        //記錄刪除節點的顏色
        int DeleteNodeColor = node->color;
        //需要調整的節點
        TreeNode* FixNode = nullptr;
        if (node->right == nullptr)
        {
            FixNode = node->left;
            TransPlant(node, node->left);
        }
        else if (node->left == nullptr)
        {
            FixNode = node->right;
            TransPlant(node, node->right);
        }
        else
        {
            //查找后繼節點
            TreeNode* tmp = increment(node);
            DeleteNodeColor = tmp->color;
            FixNode = tmp->right;
            if (tmp->parent == node)
            {
                FixNode->parent = tmp;
            }
            if (tmp->parent != node)// && tmp->right!=nullptr)
            {
                TransPlant(tmp, tmp->right);
                tmp->right = node->right;
                tmp->right->parent = tmp;
            }
            TransPlant(node, tmp);
            tmp->left = node->left;
            tmp->left->parent = tmp;
            tmp->color = node->color;
        }
        if (DeleteNodeColor == BLACK)
        {
            //處理平衡
            RBDeleteFixUp(FixNode);
        }
        resetNode(node);
    }
    void RBDeleteFixUp(TreeNode* node)
    {
        while (node != nullptr && node->color == BLACK && node != _root)
        {
            if (node == node->parent->left)
            {
                TreeNode* bnode = node->parent->right;
                //如果node為黑色節點,怎為了保持紅黑樹性質怎,其兄弟節點必定存在,即bnode不為空
                if (bnode->color == RED)
                {
                    //此時父節點一定為黑色
                    bnode->color = BLACK;
                    node->parent->color = RED;
                    RotateLeft(node->parent);
                    bnode = node->parent->right;
                }
                if (bnode->left->color == BLACK && bnode->right->color == BLACK)
                {
                    bnode->color = RED;
                    node = node->parent;
                }
                else
                {
                    if (bnode->right->color == BLACK)
                    {
                        //根據上一個條件判斷,則此時左節點必為紅色,父節點必為黑色
                        bnode->left->color = BLACK;
                        bnode->parent->color = RED;
                        RotateRight(bnode);
                        bnode = node->parent->right;
                    }
                    //如果兄弟節點的右節點為紅色
                    bnode->color = node->parent->color;
                    node->parent->color = BLACK;
                    bnode->right->color = BLACK;
                    RotateLeft(node->parent);
                    node = _root;//結束循環,達到平衡
                }
            }
            else if (node == node->parent->right)
            {
                TreeNode* bnode = node->parent->left;
                if (bnode->color == RED)
                {
                    bnode->color = BLACK;
                    node->parent->color = RED;
                    RotateRight(node->parent);
                    bnode = node->parent->left;
                }
                if (bnode->left->color == BLACK && bnode->right->color == BLACK)
                {
                    bnode->color = RED;
                    node = node->parent;
                }
                else
                {
                    if (bnode->left->color == BLACK)
                    {
                        bnode->color = RED;
                        bnode->right->color = BLACK;
                        RotateLeft(bnode);
                        bnode = node->parent->left;
                    }
                    bnode->color = node->parent->color;
                    node->parent->color = BLACK;
                    bnode->left->color = BLACK;
                    RotateLeft(node->parent);
                    node = _root;
                }
            }
        }
        if (node != nullptr)
        {
            node->color = BLACK;
        }
    }

紅黑樹測試結果:


紅黑樹測試結果.png

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