图的存储结构——邻接矩阵与邻接表

1 概述#

简单的说,图由表示数据元素的集合V和表示数据之间关系的集合E组成,记为G=<V,E>。图又分为有向图与无向图。下面是图的一些基本元素:

  • 边(edge):顶点的序偶。
  • 顶点(vertex):数据元素。
  • 权重(weight):用来表示一个顶点到另一个顶点的距离、代价、耗费等。
  • 度(degree):与该顶点相关联的边的数目,入度、出度等等。
无向图G1与有向图G2

图的基本类型常使用的有相邻矩阵与邻接表。下面将从两方面介绍图的储存结构与基本操作。

2 图的相邻矩阵储存类型#

图的相邻矩阵或邻接矩阵表示定点之间的邻接关系,即表示顶点之间有边或没有边的情况。如下图则是无向图G1和有向图G2的邻接矩阵。


(a)无向图G1的邻接矩阵 (b)有向图G2的邻接矩阵
  • 相邻矩阵类型定义

对于有向图,相邻矩阵不一定对称。因为如果相邻矩阵第i行第j列的元素为1,则表示有一条从顶点vi到顶点vj的弧,而此时不一定存在由顶点vj到顶点vi的弧。

 /*图的相邻矩阵储存类型定义*/
#define MaxVertexNum 100 /*最大顶点数设为100*/
typedef enum{FALSE,TRUE} Boolean;
Boolean mark[MaxVertexNum];

typedef char VertexType; /*顶点类型设为字符型*/
typedef int EdgeType; /*边的权值设为整型*/

typedef struct {
    VertexType vexs[MaxVertexNum]; /*顶点表*/
    EdgeType edges[MaxVertexNum][MaxVertexNum]; /*邻接矩阵,即边表*/
    int numVertex,numEdge; /*顶点数和边数*/
}Mgragh,*MGragh; /*Maragh 是以邻接矩阵存储的图类型*/
  • 图创建

首先定义相邻矩阵的尺寸,即定点数和边数。
输入每条边信息,vi为初始点,vj为终止点。将邻接表中的edges[i][j]设置为1,代表起点为i,终点为j,存在一条边。


void CreateMGraph(MGragh G){/*建立有向图G 的邻接矩阵存储*/
int i,j,k,w;
char ch;
cout<<"请输入顶点数和边数";
cin>>i>>j;
G->numVertex=i;
G->numEdge=j;

cout<<"请输入顶点信息:"<<endl;

for (i=0;i<G->numVertex;i++) {cout<<"第"<<i<<"个点:";cin>>G->vexs[i];} /*输入顶点信息,建立顶点表*/
for (i=0;i<G->numVertex;i++)
    for (j=0;j<G->numVertex;j++) 
        G->edges[i][j]=0; /*初始化邻接矩阵*/

cout<<"请输入每条边对应的两个顶点的序号:\n";

for (k=0;k<G->numEdge;k++){
    cout<<"v<i,j>:";
    cin>>i>>j; /*输入e 条边,建立邻接矩阵*/
        G->edges[i][j]=1;
            }
}
  • 判断边类

FirstEdge即返回以v为顶点的第一条边。它的想法是:返回以顶点v的编号的相邻矩阵行,从j=0开始遍历,直到搜索到矩阵元素为1时,表示i到j有一条边,于是返回此条边。

NextEdge即返回以v为顶点的下一条边。当输入边edge时,将返回以edge.from的编号相同的矩阵行,从j=edge.to+1开始遍历,直到搜索到矩阵元素为1时,表示i到j有一条边,返回此边。


Edge FirstEdge(MGragh G,int oneVertex){
   Edge myEdge;
   myEdge.from=oneVertex;
   for(int i=0;i<G->numVertex;i++){
       if(G->edges[oneVertex][i]!=0){
           myEdge.to=i;
           myEdge.weight=G->edges[oneVertex][i];
           break;
       }
   }
   return myEdge;
}

Edge NextEdge(MGragh G,Edge preEdge){
   Edge myEdge;
   myEdge.from =preEdge.from ;
   if(preEdge.to <G->numVertex){
       for(int i=preEdge.to+1;i<G->numVertex;i++){
           if(G->edges[preEdge.from][i]!=0){
               myEdge.to =i;
               myEdge.weight =G->edges[preEdge.from ][i];
               break;
           }
       }
   }
   return myEdge;
}

bool isEdge(MGragh G, Edge myEdge){
   int test=0;
   for(int i=0;i<G->numVertex;i++){
       if(myEdge.to>=0&&G->edges[myEdge.from][myEdge.to]==1)return true;
   }
   return false;
}
  • 两种周游算法

以有向图G2为例:

深度优先搜索的周游算法如下:假设从v0点出发,首先将v0的标志位设置为TRUE,然后从v0的第一条边出发,寻找第一条边的终点v2。因为v2为被访问过,因此将v2标志位设置为TRUE。从v2出发,寻找v2的第一条边终点为v3。因为v3为被访问过,因此设v3的标志位为TRUE。从v3出发,寻找v3的第一条边终点为v0,标志位为TRUE说明已被访问过。返回上一个顶点v2,寻找v2的nextEdge,无。返回v0,寻找v0的nextEdge,为v1,标识v1为TRUE。遍历完毕。

广度优先算法的周游如下:假设从v0出发。首先将v0的标志位设置为TRUE,将v0入队。因为队列不为空,所以取出front,设置为u,同时pop掉front。寻找v0的第一条边的终点v2。输出v2,并将其push入队列;寻找v0的第二条边的终点v1,,输出v1,并将其push入队列;寻找v0的第三条边的终点,无。返回取出front,将v2设置为u,pop掉它。寻找v2的第一条边v3,输出v3并将其push如队列;寻找v2的第二条边,无。返回取出front,将v1设置为u,pop掉它。寻找v1的第一条边,无。返回取出front,将v3设置为u,pop掉它,寻找v3的第一条边v0,因为以被访问,因此跳过;寻找v3的第二条边,无。算法结束。

//深度优先周游    
void DFS(MGragh G, int v){
    mark[v]=TRUE;
    cout<<G->vexs[v];
    for(Edge e=FirstEdge(G,v);isEdge(G,e);e=NextEdge(G,e)){
        if(mark[e.to]==FALSE) DFS(G,e.to);
    }
}

void DFSM(MGragh G,int v){
    for(int i=0;i<G->numVertex;i++){
        mark[i]=FALSE;
    }
    DFS(G,0);
}

//广度优先周游
void BFS(MGragh G, int v){
    for(int i=0;i<G->numVertex;i++){
        mark[i]=FALSE;
    }
    using std::queue;
    queue<int>Q;
    cout<<G->vexs[v];
    mark[v]=TRUE;
    Q.push(v);
    while(!Q.empty()){
        int u=Q.front();
        Q.pop();
        for(Edge e=FirstEdge(G,u);isEdge(G,e);e=NextEdge(G,e))
            if(mark[e.to]==FALSE){
                cout<<G->vexs[e.to];
                mark[e.to]=TRUE;
                Q.push(e.to);
            }
    }
}

3 图的邻接表储存类型#

当图中的边数较少时,相邻矩阵就会出现大量的0元素,储存这些0元素将消耗大量的储存空间。
邻接表表示法是一种链式存储结构,由一个顺序储存的顶点表和n个链表储存的边表组成。顶点表目有两个域:顶点数据域和指向此顶点边表指针域。边表把依附于同一个顶点vi的边(即相邻矩阵中同一行的非0元素)组织成一个单链表。边表中的每一个表目都代表一条边,由两个主要的域组成:与顶点vi邻接的另一个顶点的序号、指向边表中下一个边表的目的指针。
顶点结点和边结点的结构如下:

Paste_Image.png

与邻接矩阵储存类型相似,邻接表的基本函数依然包括FirstEdge,NextEdge和isEdge。这里不再重复,下面只给出广度周游和深度周游算法。

  • 邻接表类型定义
using namespace std;
#define MAX_VERTEXT_NUM 20
typedef enum{FALSE,TRUE} Boolean;
Boolean mark[MAX_VERTEXT_NUM];

typedef struct edge{     /*边定义*/
    int from,to,weight;
}Edge,*Edged;

typedef struct ArcNode{     /*边结点定义*/
    int adjvex;
    struct ArcNode *nextArc;
    int weight;
}ArcNode;

typedef struct VNode{     /*顶点定义*/
    char data;
    ArcNode *firstArc;
}VNode, AdjList[MAX_VERTEXT_NUM];

typedef struct Indegree{     /*每个点的入度*/
    int indegree;
}Indegree,indegree[MAX_VERTEXT_NUM];

typedef struct{     /*图定义*/
    AdjList verTices;
    int vexNum;
    int arcNum;
    int kind;
    indegree Indegree;
}ALGraph;
  • 图的邻接表储存类型建立

首先输入顶点和边的个数,同时申请顶点个数的结点空间。
每次输入边的邻接关系时i-j时,首先申请一个边结点空间给,结点的adjvex为j,nextarc为头结点firstarc的nextarc,头结点的firstarc变为此j结点arcnode。

邻接表插入顶点
void CreateGraph(ALGraph *G)
{
    int i,j,k,weight;
    ArcNode *arcNode;
    printf_s("请输入顶点数和边数:");
    cin>>G->vexNum;
    cin>>G->arcNum;
    //建立顶点表
    printf_s("建立顶点表\n");
    for (i = 0; i < G->vexNum; i++)
    {
        printf_s("请输入第%d个顶点:", i);
        cin>>G->verTices[i].data;
        arcNode = (ArcNode *)malloc(sizeof(ArcNode));
        arcNode->adjvex=i;
        arcNode->nextArc=NULL;
        G->verTices[i].firstArc=arcNode;
        G->Indegree[i].indegree=0;
    }
    //建立边表
    printf_s("建立边表\n");
    for (k = 0; k < G->arcNum; k++)
    {
        printf_s("请输入(vi-vj)的顶点对序号");
        cin>>i;
        cin>>j;
        arcNode = (ArcNode *)malloc(sizeof(ArcNode));
        arcNode->adjvex = j;
        arcNode->nextArc = G->verTices[i].firstArc->nextArc;//插入表头
        G->verTices[i].firstArc ->nextArc= arcNode;
        G->Indegree[j].indegree++;
    }
}
  • 两种周游算法

深度优先周游:对于邻接表存储类型,它采用递归算法,从起始点v0沿着邻接表一次访问,直到next指针为NULL,则返回上一层,进入上一层顶点v1所在的链表继续逐个访问。

广度周游与相邻矩阵法类似,这里不做阐述。算法如下:

//深度优先周游
void DFSM(ALGraph *G,int i){
    ArcNode *p;
    cout<<G->verTices[i].data;
    mark[i]=TRUE;
    p=G->verTices[i].firstArc;
    while(p){
        if(mark[p->adjvex]==FALSE)
            DFSM(G,p->adjvex);
        p=p->nextArc;       
    }
}

void DFS(ALGraph *G){
    for ( int i=0; i<G->vexNum;i++)
        mark[i]=FALSE;
    for( int i=0; i<G->vexNum;i++)
        if(!mark[i])
            DFSM(G,i);
}

//广度优先周游
void BFS(ALGraph *G,int x){
    ArcNode *p;
    int w;
    using std::queue;
    queue<int> Q;
    for ( int i=0; i<G->vexNum;i++) {mark[i]=FALSE;}
    cout<<G->verTices[x].data;
    mark[x]=TRUE;
    Q.push(x);
    while(!Q.empty()){
        int v=Q.front();
        Q.pop();
        p=G->verTices[v].firstArc;
        while(p){
            int w=p->adjvex;
            if(mark[w]==FALSE){
                mark[w]=TRUE;
                cout<<G->verTices[w].data;
                Q.push(w);}
            p=p->nextArc;
            }
        }
}
  • 拓扑排序

有向图的边可以看做定点之间制约关系的描述。在工程实践中,有些工程的进行经常受到一定条件的约束,例如一个工程项目通常由若干子工程组成,某些子工程完成之后另一些子工程才能开始。

一个无环的有向图称为有向无环图,有向无环图常用来描述一个过程或一个系统的进行过程。对于有向无环图G=<V,E>,如果顶点序列满足:存在顶点vi到vj的一条路径,那么在序列中顶点vi必在顶点vj之前,顶点集合V的这种线型序列称作一个拓扑序列。

进行有向图的拓扑序列方法如下:

  1. 从有向图中选出一个没有前驱(入度为0)的顶点并输出。
  2. 删除图中该顶点和所有以它为起点的弧。

不断重复上述两个步骤,会出现两种情形:要么有向图中顶点全部被输出,要么当前图中不存在没有前驱的顶点。当图中的顶点全部输出时,就完成了有向无环图的拓扑排序;当图中还有顶点没有输出时,说明有向图中含有环。

它的工作思想如下:

拓扑排序

//拓扑排序
void TopsortbyQueue(ALGraph*G){
    for(int i=0; i< G->vexNum;i++)
        mark[i]=FALSE;
    using std::queue;
    queue<int> Q;
    cout<<"拓扑序列为:"<<endl;
    for(int i=0; i< G->vexNum;i++){
        if(G->Indegree[i].indegree==0)
            Q.push(i);}
        while(!Q.empty()){
            int v=Q.front();
            Q.pop();
            cout<<v;
            mark[v]=TRUE;
            for(Edge e=FirstEdge(G,v);isEdge(G,e);e=NextEdge(G,e)){
                G->Indegree[e.to].indegree--;
                if(G->Indegree[e.to].indegree ==0)
                    Q.push(e.to);
            }
        }
        for(int i=0;i< G->vexNum;i++)
            if(mark[i]==FALSE){
                cout<<endl;
                cout<<"还有顶点未访问,此图有环。"<<endl;
                break;
            }
}

4 总结#

明显感觉到写得多了对于语言的运用更熟练了,也更有了设计算法需要的逻辑思想。这个写的还算比较顺随着思维和语言能力提升,希望能在acm校选赛比赛上有好的发挥过两天来更新结果!

5 附录#

邻接矩阵:

邻接矩阵测试结果
#include<stdio.h>
#include<stdlib.h>
#include<iostream>
#include<queue> 
using namespace std;

#define MaxVertexNum 100 /*最大顶点数设为100*/
typedef enum{FALSE,TRUE} Boolean;
Boolean mark[MaxVertexNum];

typedef char VertexType; /*顶点类型设为字符型*/
typedef int EdgeType; /*边的权值设为整型*/

typedef struct {
    VertexType vexs[MaxVertexNum]; /*顶点表*/
    EdgeType edges[MaxVertexNum][MaxVertexNum]; /*邻接矩阵,即边表*/
    int numVertex,numEdge; /*顶点数和边数*/
}Mgragh,*MGragh; /*Maragh 是以邻接矩阵存储的图类型*/


typedef struct edge{
    int from,to,weight;
}Edge,*Edged;

void CreateMGraph(MGragh G){/*建立有向图G 的邻接矩阵存储*/
int i,j,k,w;
char ch;
cout<<"请输入顶点数和边数";
cin>>i>>j;
G->numVertex=i;
G->numEdge=j;

cout<<"请输入顶点信息:"<<endl;

for (i=0;i<G->numVertex;i++) {cout<<"第"<<i<<"个点:";cin>>G->vexs[i];} /*输入顶点信息,建立顶点表*/
for (i=0;i<G->numVertex;i++)
    for (j=0;j<G->numVertex;j++) 
        G->edges[i][j]=0; /*初始化邻接矩阵*/

cout<<"请输入每条边对应的两个顶点的序号:\n";

for (k=0;k<G->numEdge;k++){
    cout<<"v<i,j>:";
    cin>>i>>j; /*输入e 条边,建立邻接矩阵*/
        G->edges[i][j]=1;
            }
}

void displaygraph(MGragh G){
int i,j;

for(i=0;i<G->numVertex;i++){
    for(j=0;j<G->numVertex;j++){
        cout<<G->edges[i][j]<<" ";}
    cout<<endl;
}
}

Edge FirstEdge(MGragh G,int oneVertex){
    Edge myEdge;
    myEdge.from=oneVertex;
    for(int i=0;i<G->numVertex;i++){
        if(G->edges[oneVertex][i]!=0){
            myEdge.to=i;
            myEdge.weight=G->edges[oneVertex][i];
            break;
        }
    }
    return myEdge;
}

Edge NextEdge(MGragh G,Edge preEdge){
    Edge myEdge;
    myEdge.from =preEdge.from ;
    if(preEdge.to <G->numVertex){
        for(int i=preEdge.to+1;i<G->numVertex;i++){
            if(G->edges[preEdge.from][i]!=0){
                myEdge.to =i;
                myEdge.weight =G->edges[preEdge.from ][i];
                break;
            }
        }
    }
    return myEdge;
}

bool isEdge(MGragh G, Edge myEdge){
    int test=0;
    for(int i=0;i<G->numVertex;i++){
        if(myEdge.to>=0&&G->edges[myEdge.from][myEdge.to]==1)return true;
    }
    return false;
}

//深度优先周游    
void DFS(MGragh G, int v){
    mark[v]=TRUE;
    cout<<G->vexs[v];
    for(Edge e=FirstEdge(G,v);isEdge(G,e);e=NextEdge(G,e)){
        if(mark[e.to]==FALSE) DFS(G,e.to);
    }
}

void DFSM(MGragh G,int v){
    for(int i=0;i<G->numVertex;i++){
        mark[i]=FALSE;
    }
    DFS(G,0);
}

//广度优先周游
void BFS(MGragh G, int v){
    for(int i=0;i<G->numVertex;i++){
        mark[i]=FALSE;
    }
    using std::queue;
    queue<int>Q;
    cout<<G->vexs[v];
    mark[v]=TRUE;
    Q.push(v);
    while(!Q.empty()){
        int u=Q.front();
        Q.pop();
        for(Edge e=FirstEdge(G,u);isEdge(G,e);e=NextEdge(G,e))
            if(mark[e.to]==FALSE){
                cout<<G->vexs[e.to];
                mark[e.to]=TRUE;
                Q.push(e.to);
            }
    }
}

int main(){
    Mgragh *Graph = (Mgragh *)malloc(sizeof(Mgragh));
    CreateMGraph(Graph);
    displaygraph(Graph);
    cout<<endl;
    BFS(Graph,0);
    cout<<endl;
    DFSM(Graph,0);
    system("pause");
    return 0;
}

邻接表

邻接表
#include<stdio.h>
#include<stdlib.h>
#include<iostream>
#include<queue> 
using namespace std;
#define MAX_VERTEXT_NUM 20
typedef enum{FALSE,TRUE} Boolean;
Boolean mark[MAX_VERTEXT_NUM];

typedef struct edge{
    int from,to,weight;
}Edge,*Edged;

typedef struct ArcNode{
    int adjvex;
    struct ArcNode *nextArc;
    int weight;
}ArcNode;

typedef struct VNode{
    char data;
    ArcNode *firstArc;
}VNode, AdjList[MAX_VERTEXT_NUM];

typedef struct Indegree{
    int indegree;
}Indegree,indegree[MAX_VERTEXT_NUM];

typedef struct{
    AdjList verTices;
    int vexNum;
    int arcNum;
    int kind;
    indegree Indegree;
}ALGraph;

typedef struct Dist{
    int index;
    int length;
    int pre;
}Dist,*Dijk;

edge FirstEdge(ALGraph *G,int oneVertex){
    Edge myEdge;
    myEdge.from=oneVertex;
    ArcNode *temp=G->verTices[oneVertex].firstArc;
    if(temp->nextArc!=NULL){
        myEdge.to=temp->nextArc->adjvex;
        myEdge.weight=temp->nextArc->weight;
    }
    return myEdge;
}

edge NextEdge(ALGraph *G,Edge preEdge){
    Edge myEdge;
    myEdge.from =preEdge.from;
    ArcNode *temp=G->verTices[preEdge.from].firstArc;
    while(temp->nextArc!=NULL&&temp->adjvex<=preEdge.to)
        temp=temp->nextArc;
    if(temp->nextArc!=NULL){
        myEdge.to=temp->nextArc->adjvex;
        myEdge.weight=temp->nextArc->weight;
    }
    return myEdge;
}

bool isEdge(ALGraph *G, Edge myEdge){
    int n=0;
    ArcNode *p;
    p=G->verTices[myEdge.from].firstArc;
    while(p){
        if(p->adjvex==myEdge.to){
            n=0;
        }else{
            n=1;
        }
        p=p->nextArc;
    }
    if(n==0){return true;}
    else{
        return false;
    }
}

void CreateGraph(ALGraph *G)
{
    int i,j,k,weight;
    ArcNode *arcNode;
    printf_s("请输入顶点数和边数:");
    cin>>G->vexNum;
    cin>>G->arcNum;
    //建立顶点表
    printf_s("建立顶点表\n");
    for (i = 0; i < G->vexNum; i++)
    {
        printf_s("请输入第%d个顶点:", i);
        cin>>G->verTices[i].data;
        arcNode = (ArcNode *)malloc(sizeof(ArcNode));
        arcNode->adjvex=i;
        arcNode->nextArc=NULL;
        G->verTices[i].firstArc=arcNode;
        G->Indegree[i].indegree=0;
    }
    //建立边表
    printf_s("建立边表\n");
    for (k = 0; k < G->arcNum; k++)
    {
        printf_s("请输入(vi-vj)的顶点对序号");
        cin>>i;
        cin>>j;
        arcNode = (ArcNode *)malloc(sizeof(ArcNode));
        arcNode->adjvex = j;
        arcNode->nextArc = G->verTices[i].firstArc->nextArc;//插入表头
        G->verTices[i].firstArc ->nextArc= arcNode;
        G->Indegree[j].indegree++;
    }
}

//显示图的邻接表
void DisplayGraph(ALGraph *G)
{
    int i;
    for (i = 0; i < G->vexNum; i++)
    {
            cout<<G->Indegree[i].indegree;
            ArcNode *P= G->verTices[i].firstArc;
            printf_s("%d->", i);
        while (P != NULL)
        {
            printf_s("%d->", P->adjvex);
            P = P->nextArc;
        }
        printf_s("\n");
    }
}

//深度优先周游
void DFSM(ALGraph *G,int i){
    ArcNode *p;
    cout<<G->verTices[i].data;
    mark[i]=TRUE;
    p=G->verTices[i].firstArc;
    while(p){
        if(mark[p->adjvex]==FALSE)
            DFSM(G,p->adjvex);
        p=p->nextArc;       
    }
}

void DFS(ALGraph *G){
    for ( int i=0; i<G->vexNum;i++)
        mark[i]=FALSE;
    for( int i=0; i<G->vexNum;i++)
        if(!mark[i])
            DFSM(G,i);
}

//广度优先周游
void BFS(ALGraph *G,int x){
    ArcNode *p;
    int w;
    using std::queue;
    queue<int> Q;
    for ( int i=0; i<G->vexNum;i++) {mark[i]=FALSE;}
    cout<<G->verTices[x].data;
    mark[x]=TRUE;
    Q.push(x);
    while(!Q.empty()){
        int v=Q.front();
        Q.pop();
        p=G->verTices[v].firstArc;
        while(p){
            int w=p->adjvex;
            if(mark[w]==FALSE){
                mark[w]=TRUE;
                cout<<G->verTices[w].data;
                Q.push(w);}
            p=p->nextArc;
            }
        }
}

//拓扑排序
void TopsortbyQueue(ALGraph*G){
    for(int i=0; i< G->vexNum;i++)
        mark[i]=FALSE;
    using std::queue;
    queue<int> Q;
    cout<<"拓扑序列为:"<<endl;
    for(int i=0; i< G->vexNum;i++){
        if(G->Indegree[i].indegree==0)
            Q.push(i);}
        while(!Q.empty()){
            int v=Q.front();
            Q.pop();
            cout<<v;
            mark[v]=TRUE;
            for(Edge e=FirstEdge(G,v);isEdge(G,e);e=NextEdge(G,e)){
                G->Indegree[e.to].indegree--;
                if(G->Indegree[e.to].indegree ==0)
                    Q.push(e.to);
            }
        }
        for(int i=0;i< G->vexNum;i++)
            if(mark[i]==FALSE){
                cout<<endl;
                cout<<"还有顶点未访问,此图有环。"<<endl;
                break;
            }
}

int main(){
    int x;
    int num;
    string edge;

    ALGraph *Graph = (ALGraph *)malloc(sizeof(ALGraph));
    CreateGraph(Graph);
    DisplayGraph(Graph);
    DFS(Graph);
    BFS(Graph,0);

    TopsortbyQueue(Graph);
    system("pause");
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,219评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,363评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,933评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,020评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,400评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,640评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,896评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,597评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,327评论 1 244
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,581评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,072评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,399评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,054评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,849评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,672评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,585评论 2 270

推荐阅读更多精彩内容