常見的排序算法

總結一下常見的排序算法。 排序分內排序和外排序。內排序:指在排序期間數據對象全部存放在內存的排序。外排序:指在排序期間全部對象個數太多,不能同時存放在內存,必須根據排序過程的要求,不斷在內、外存之間移動的排序。內排序的方法有許多種,按所用策略不同,可歸納為五類:插入排序、選擇排序、交換排序、歸并排序、分配排序和計數排序。插入排序主要包括直接插入排序,折半插入排序和希爾排序兩種;選擇排序主要包括直接選擇排序和堆排序;交換排序主要包括冒泡排序和快速排序;歸并排序主要包括二路歸并(常用的歸并排序)和自然歸并。分配排序主要包括箱排序和基數排序。計數排序就一種。
穩定排序:假設在待排序的文件中,存在兩個或兩個以上的記錄具有相同的關鍵字,在用某種排序法排序后,若這些相同關鍵字的元素的相對次序仍然不變,則這種排序方法是穩定的。其中冒泡,插入,基數,歸并屬于穩定排序;選擇,快速,希爾,堆屬于不穩定排序。 下面是對這些常見排序算法的介紹。未來測試代碼的正確性,我們采取了隨機生成10個序列,然后先使用C++STL中給出的排序算法sort來得到一個正確的排序,然后再使用我們的方法進行排序得到結果,通過對比這兩者的結果來驗證我們代碼的正確性。
測試代碼如下:

復制代碼

void sort_test(void (_sort)(int,int)){ const int N=10; int orig[N]; int standard[N]; int arr[N]; srand(time(0)); for(int j=0;j<15;j++){ for(int i=0;i<N;i++) orig[i]=rand()%100;//隨機生成序列 cout<<"bef:"; print(orig,N); copy(orig,orig+N,standard); sort(standard,standard+N);//利用sort函數進行排序 cout<<"std:"; print(standard,N); copy(orig,orig+N,arr); _sort(arr,N);//采用我們的方法進行排序 cout<<"aft:"; print(arr,N); if(equal(standard,standard+N,arr))//測試我們的方法是否正確 printf("%sOK%s\n",green,normal); else printf("%sNO%s\n",red,normal); }}
復制代碼

其中參數是要測試的方法,void (_sort)(int,int)是排序方法的指針,我們所有的排序方法都寫成這種形式。

  1. 直接插入排序 直接插入排序(straight insertion sort)的作法是:每次從無序表中取出第一個元素,把它插入到有序表的合適位置,使有序表仍然有序。
      第一趟比較前兩個數,然后把第二個數按大小插入到有序表中; 第二趟把第三個數據與前兩個數從后向前掃描,把第三個數按大小插入到有序表中;依次進行下去,進行了(n-1)趟掃描以后就完成了整個排序過程。
      直接插入排序屬于穩定的排序,時間復雜性為o(n^2),空間復雜度為O(1)。
      直接插入排序是由兩層嵌套循環組成的。外層循環標識并決定待比較的數值。 內層循環為待比較數值確定其最終位置。直接插入排序是將待比較的數值與它的前一個數值進行比較,所以外層循環是從第二個數值開始的。當前一數值比待比較數 值大的情況下繼續循環比較,直到找到比待比較數值小的并將待比較數值置入其后一位置,結束該次循環。(從小到大)
    值得注意的是,我們必需用一個存儲空間來保存當前待比較的數值,因為當一趟比較完成時,我們要將待比較數值置入比它小的數值的后一位。插入排序類似玩牌時整理手中紙牌的過程。

代碼如下:


復制代碼

void insert_sort(int a[],int n){ _FUNC; for(int i=1;i<n;i++) { int t=a[i]; int j; for(j=i-1;j>=0&&a[j]>t;j--) { a[j+1]=a[j]; } a[j+1]=t; print(a,n); }}


復制代碼

測試結果如下:


  1. 折半插入排序
    折半插入排序(binary insertion sort)是對插入排序算法的一種改進,由于排序算法過程中,就是不斷的依次將元素插入前面已排好序的序列中。由于前半部分為已排好序的數列,這樣我們不用按順序依次尋找插入點,可以采用折半查找的方法來加快尋找插入點的速度。
    折半插入排序算法的具體操作為:在將一個新元素插入已排好序的數組的過程中,尋找插入點時,將待插入區域的首元素設置為a[low],末元素設置為 a[high],則輪比較時將待插入元素與a[m],其中m=(low+high)/2相比較,如果比參考元素小,則選擇a[low]到a[m-1]為新 的插入區域(即high=m-1),否則選擇a[m+1]到a[high]為新的插入區域(即low=m+1),如此直至low<=high不成 立,即將此位置之后所有元素后移一位,并將新元素插入a[high+1]。
    折半插入排序算法是一種穩定的排序算法,比直接插入算法明顯減少了關鍵字之間比較的次數,因此速度比直接插入排序算法快,但記錄移動的次數沒有變,所以折半插入排序算法的時間復雜度仍然為O(n^2),與直接插入排序算法相同。
    代碼如下:


    復制代碼

    void binary_insert_sort(int a[],int n){ for(int i=1;i<n;i++){ int low=0; int high=i-1; int t=a[i]; int mid; while(low<=high){ mid=(low+high)/2; if(t<a[mid]) high=mid-1; else low=mid+1; } for(int j=i;j>mid;j--) a[j]=a[j-1]; a[low]=t; }}


    復制代碼

測試結果如下:


  1. 希爾排序
    希爾排序(Shell Sort)又叫做縮小增量排序(diminishing increment sort),是一種很優秀的排序法,算法本身不難理解,也很容易實現,而且它的速度很快。 基本思想:
      先取一個小于n的整數d1作為第一個增量,把文件的全部記錄分成d1個組。所有距離為dl的倍數的記錄放在同一個組中。先在各組內進行直接插入 排序;然后,取第二個增量d2<d1重復上述的分組和排序,直至所取的增量dt=1(dt<dt-l<…<d2<d1), 即所有記錄放在同一組中進行直接插入排序為止。
      該方法實質上是一種分組插入方法。插入排序(Insertion Sort)的一個重要的特點是,如果原始數據的大部分元素已經排序,那么插入排序的速度很快(因為需要移動的元素很少)。從這個事實我們可以想到,如果原 始數據只有很少元素,那么排序的速度也很快。--希爾排序就是基于這兩點對插入排序作出了改進。
    下圖是希爾排序的一種實現方式:


該圖對應的實現方式如下:
復制代碼

void shell_sort(int a[],int n){ _FUNC; int gap=n/2; bool flag=true; while(gap>1||flag) { flag=false; for(int i=0;i+gap<n;i++) if(a[i]>a[i+gap]) { swap(a[i],a[i+gap]); flag=true; } print(a,n); if(gap>1) gap/=2; }}


復制代碼

測試結果如下:



另一種實現方式:


復制代碼

void shell_sort2(int a[],int n){// _FUNC; int gap=n/2; while(gap>0){ for(int i=gap;i<n;i++){ int t=a[i]; int j; for(j=i-gap;j>=0&&a[j]>t;j-=gap) a[j+gap]=a[j]; a[j+gap]=t; } gap/=2; }}
復制代碼
  1. 直接選擇排序
    排序是給每個位置選擇當前元素最小的,比如給第一個位置選擇最小的,在剩余元素里面給第二個元素選擇第二小的,依次類推,直到第n-1個元素,第n個 元素不用選擇了,因為只剩下它一個最大的元素了。那么,在一趟選擇,如果當前元素比一個元素小,而該小的元素又出現在一個和當前元素相等的元素后面,那么 交換后穩定性就被破壞了。比較拗口,舉個例子,序列5 8 5 2 9,我們知道第一遍選擇第1個元素5會和2交換,那么原序列中2個5的相對前后順序就被破壞了,所以選擇排序不是一個穩定的排序算法。時間復雜度是O(n^2)
    代碼如下:


    復制代碼

    void select_sort(int a[],int n){ for(int i=0;i<n-1;i++) { int min=a[i]; int index=i; for(int j=i+1;j<n;j++) if(a[j]<min) { min=a[j]; index=j; } swap(a[i],a[index]); }}


    復制代碼

這個是最基本的:從中找出最小的然后和第一個數交換,再從第2到n-1中找出最小的和第二個數交換
方法二:


復制代碼

void select_sort2(int a[],int n){ _FUNC; for(int i=n-1;i>0;i--){ for(int j=0;j<i;j++) if(a[j]>a[i]) swap(a[j],a[i]); }}


復制代碼

這兒感覺形式上有點類似下面的冒泡排序。
方法三:
這是對方法二的改進,判斷過程中是否有交換發生,如果沒有交換,說明已經完成排序了。


復制代碼

void select_sort3(int a[],int n){ _FUNC; bool flag=true; for(int i=n-1;i>0&&flag;i--){ flag=false; for(int j=0;j<i;j++) if(a[j]>a[i]) swap(a[j],a[i]),flag=true; print(a,n); }}


復制代碼

5. 堆排序
我們知道堆的結構是節點i的孩子為2i和2i+1節點,大頂堆要求父節點大于等于其2個子節點,小頂堆要求父節點小于等于其2個子節點。在一個長為n 的序列,堆排序的過程是從第n/2開始和其子節點共3個值選擇最大(大頂堆)或者最小(小頂堆),這3個元素之間的選擇當然不會破壞穩定性。但當為n /2-1, n/2-2, ...1這些個父節點選擇元素時,就會破壞穩定性。有可能第n/2個父節點交換把后面一個元素交換過去了,而第n/2-1個父節點把后面一個相同的元素沒 有交換,那么這2個相同的元素之間的穩定性就被破壞了。所以,堆排序不是穩定的排序算法。
堆排序的代碼如下:

復制代碼

void adjust(int b[],int m,int n){// int b=a-1; int j=m; int k=2m; while(k<=n){ if(k<n&&b[k]<b[k+1]) k++; if(b[j]<b[k]) swap(b[j],b[k]); j=k; k*=2; }}void heap_sort(int a[],int n){ _FUNC; int *b=a-1; for(int i=n/2;i>=1;i--) adjust(b,i,n); for(int i=n-1;i>=1;i--){ swap(b[1],b[i+1]); adjust(b,1,i); }}
復制代碼

需要注意的是,如果使用數組表示堆的話,要從下標1開始,而不是從0開始。所以,這兒采用了一個技巧,讓int*b=a-1;這樣的話b[1]就相當于對原數組從0開始似的,即a[0]。

  1. 冒泡排序
    冒泡排序就是把小的元素往前調或者把大的元素往后調。比較是相鄰的兩個元素比較,交換也發生在這兩個元素之間。所以,如果兩個元素相等,是不用交換的;如果兩個相等的元素沒有相鄰,那么即使通過前面的兩兩交換把兩個相鄰起來,這時候也不會交換,所以相同元素的前后順序并沒有改 變,所以冒泡排序是一種穩定排序算法。
    代碼如下:


    復制代碼

    void bubble_sort(int a[],int n){ _FUNC; for(int i=n-1;i>0;i--) for(int j=0;j<i;j++) if(a[j]>a[j+1]) swap(a[j],a[j+1]);}


    復制代碼

下面的方法是加入了是否已經排好序的判斷。

復制代碼

void bubble_sort2(int a[],int n){ bool flag=true; for(int i=n-1;i>0&&flag;i--){ flag=false; for(int j=0;j<i;j++) if(a[j]>a[j+1]) swap(a[j],a[j+1]),flag=true; }}
復制代碼

  1. 快速排序
    快速排序(Quicksort)是對冒泡排序的一種改進。由C. A. R. Hoare在1962年提出。它的基本思想是:通過一趟排序將要排序的數據分割成獨立的兩部分,其中一部分的所有數據都比另外一部分的所有數據都要小,然 后再按此方法對這兩部分數據分別進行快速排序,整個排序過程可以遞歸進行,以此達到整個數據變成有序序列。



    快速排序有兩個方向,左邊的i下標一直往右走,當a[i] <= a[center_index],其中center_index是中樞元素的數組下標,一般取為數組第0個元素。而右邊的j下標一直往左走,當a[j] > a[center_index]。如果i和j都走不動了,i <= j, 交換a[i]和a[j],重復上面的過程,直到i>j。交換a[j]和a[center_index],完成一趟快速排序。在中樞元素和a[j]交 換的時候,很有可能把前面的元素的穩定性打亂,比如序列為 5 3 3 4 3 8 9 10 11,現在中樞元素5和3(第5個元素,下標從1開始計)交換就會把元素3的穩定性打亂,所以快速排序是一個不穩定的排序算法,不穩定發生在中樞元素和 a[j] 交換的時刻。
    下面的代碼中中樞元素采用的中間的元素:


    復制代碼

    void qsort(int a[],int l,int r){ int pvt=a[(l+r)/2]; int i=l,j=r; while(i<=j){ while(a[i]<pvt) i++; while(a[j]>pvt) j--; if(i<=j){ if(i!=j) swap(a[i],a[j]); i++; j--; } } if(j>l) qsort(a,l,j); if(i<r) qsort(a,i,r);}void quick_sort(int a[],int n){ qsort(a,0,n-1);}
    復制代碼
  1. 二路歸并排序

歸并排序是把序列遞歸地分成短序列,遞歸出口是短序列只有1個元素(認為直接有序)或者2個序列(1次比較和交換),然后把各個有序的段序列合并成一個有 序的長序列,不斷合并直到原序列全部排好序。可以發現,在1個或2個元素時,1個元素不會交換,2個元素如果大小相等也沒有人故意交換,這不會破壞穩定 性。那么,在短的有序序列合并的過程中,穩定是是否受到破壞?沒有,合并過程中我們可以保證如果兩個當前元素相等時,我們把處在前面的序列的元素保存在結 果序列的前面,這樣就保證了穩定性。所以,歸并排序也是穩定的排序算法。




方法一:遞歸形式的歸并排序
復制代碼

void merge(int a[],int b[],int l,int m,int r){// int *b=new int[r-l+1]; int i,j,k; i=l; j=m+1; k=l; while(i<=m&&j<=r){ if(a[i]<a[j]) b[k++]=a[i++]; else b[k++]=a[j++]; } while(i<=m) b[k++]=a[i++]; while(j<=r) b[k++]=a[j++]; for(int s=l;s<=r;s++) a[s]=b[s];// delete[] b;}void msort(int a[],int b[],int l,int r){ if(l<r){ int m=(l+r)/2; msort(a,b,l,m); msort(a,b,m+1,r); merge(a,b,l,m,r); }}void merge_sort(int a[],int n){ _FUNC; int *b=new int[n]; msort(a,b,0,n-1); delete[] b;}
復制代碼

方法二:去除遞歸的方法

復制代碼

void merge_pass(int x[],int y[],int s,int n){ int i=0; while(i+2s-1<n){ merge(x,y,i,i+s-1,i+2s-1); i+=2*s; } if(i+s<n) merge(x,y,i,i+s-1,n-1); else for(int j=i;j<=n-1;j++) y[j]=x[j];}void merge_sort2(int a[],int n){ _FUNC; int *b=new int [n]; int s=1; while(s<n){ merge_pass(a,b,s,n); s+=s; merge_pass(b,a,s,n); s+=s; } delete[] b;}
復制代碼

  1. 自然歸并排序



    下面的兩種形式是一樣的,開始我先采用的vector來記錄子序列的位置,后來發現其實采用一個數組就可以了。兩種代碼都放在這兒吧。
    形式1:


    復制代碼

    void merge_sort3(int a[],int n){ vector<int> st; for(int i=0;i<n-1;i++){ if(a[i]>a[i+1]) st.push_back(i); } st.push_back(n-1);// copy(st.begin(),st.end(),ostream_iterator<int>(cout," "));// cout<<endl; int *b=new int [n]; int l,m,r; l=0; if(!st.empty()) { m=st.front(); st.erase(st.begin()); } while(!st.empty()){ r=st.front(); st.erase(st.begin()); merge(a,b,l,m,r);// print(a,n);// copy(st.begin(),st.end(),ostream_iterator<int>(cout," "));// cout<<endl; m=r; }// print(a,n); delete [] b;}
    復制代碼

形式2:

復制代碼

void merge_sort4(int a[],int n){ _FUNC; int *pos=new int[n]; int k=0; for(int i=0;i<n-1;i++){ if(a[i]>a[i+1]) pos[k++]=i; } pos[k++]=n-1; int *b=new int [n]; int l,m,r; l=0; int p=0; if(p<k) m=pos[p++]; while(p<k){ r=pos[p++]; merge(a,b,l,m,r); m=r; } delete [] b;}
復制代碼

  1. 箱排序



一個簡單的測試例子如下:

View Code
上面的程序采用一個簡單的Node類來描述學生的姓名和成績,采用STL中的list來實現箱子排序。同樣進行隨機生成了多個實例來測試程序的正確性。而所采用的標準是STL中的multimap容器。因為這個容器可以自動根據關鍵字進行排序。本來想使用map容器,但是map容器不允許重復,而我們的測試實例中有很多的重復元素。測試的部分結果如下:

  1. 基數排序
    基數排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次類推,直到最高位。有時候有些屬性是有優先級順序的,先按低優先級排序,再按高優 先級排序,最后的次序就是高優先級高的在前,高優先級相同的低優先級高的在前。基數排序基于分別排序,分別收集,所以其是穩定的排序算法。

下面的一種方法是采用STL的鏈表容器list來實現的,這種實現比較直觀:

復制代碼

void radix_sort2(int a[],int n){ int bits=maxbits(a,n); list<int> x(a,a+n); int range=10; vector<list<int> > bin(range); list<int> y; list<int>::iterator ite; int adix=1; for(int i=0;i<bits;i++){ for(ite=x.begin();ite!=x.end();ite++){ int d=(ite/adix)%10; bin[d].push_back(ite); } vector<list<int> >::iterator ite2; y.clear(); for(ite2=bin.begin();ite2!=bin.end();++ite2){ for(ite=ite2->begin();ite!=ite2->end();++ite) y.push_back(ite); ite2->clear(); } x=y; adix=10; } int i=0; for(ite=x.begin();ite!=x.end();ite++) a[i++]=*ite;}
復制代碼

另一種方法是采用多個數組來實現,不是很容易理解,這是參考的網上的代碼。具體代碼如下:

復制代碼

int maxbits(int a[],int n){ int d=0; for(int i=0;i<n;i++){ int b=1; int r=a[i]; while(r/10>0){ b++; r/=10; } if(d<b) d=b; } return d;}void radix_sort(int a[],int n){ _FUNC; int d=maxbits(a,n); int *temp=new int[n]; int count=new int[10]; int adix=1; for(int b=1;b<=d;b++){ for(int i=0;i<10;i++) count[i]=0; for(int i=0;i<n;i++){ int k=(a[i]/adix)%10; count[k]++; } for(int i=1;i<10;i++) count[i]+=count[i-1]; for(int i=n-1;i>=0;i--){ int k=(a[i]/adix)%10; count[k]--; temp[count[k]]=a[i]; } for(int i=0;i<n;i++) a[i]=temp[i]; adix=10; } delete[] temp; delete[] count;}
復制代碼

12.計數排序


代碼如下:
復制代碼

void rank(int arr[],int n,int r[]){ for(int i=0;i<n;i++) r[i]=0; for(int i=1;i<n;i++){ for(int j=0;j<i;j++) { if(arr[j]<=arr[i]) r[i]++; else r[j]++; } }}
復制代碼


代碼如下:
復制代碼

void rank_sort2(int a[],int n){ int *r=new int[n]; rank(a,n,r); int *u=new int[n]; for(int i=0;i<n;i++) u[r[i]]=a[i]; for(int i=0;i<n;i++) a[i]=u[i]; delete[] r; delete[] u;}


復制代碼

代碼如下:


復制代碼

void rank_sort(int arr[],int n){ int *r=new int[n]; rank(arr,n,r); for(int i=0;i<n;i++) { while(r[i]!=i) { int t=r[i]; swap(arr[i],arr[t]); swap(r[i],r[t]); } } delete[] r;}


復制代碼

排序算法復雜度:
按平均時間將排序分為四類:(1)平方階(O(n2
))排序  一般稱為簡單排序,例如直接插入、直接選擇和冒泡排序;(2)線性對數階(O(nlgn))排序  如快速、堆和歸并排序;(3)O(n1+£
)階排序  £是介于0和1之間的常數,即0<£<1,如希爾排序;(4)線性階(O(n))排序  如桶、箱和基數排序。


不同條件下,排序方法的選擇(1)若n較小(如n≤50),可采用直接插入或直接選擇排序。  當記錄規模較小時,直接插入排序較好;否則因為直接選擇移動的記錄數少于直接插人,應選直接選擇排序為宜。(2)若文件初始狀態基本有序(指正序),則應選用直接插人、冒泡或隨機的快速排序為宜;(3)若n較大,則應采用時間復雜度為O(nlgn)的排序方法:快速排序、堆排序或歸并排序。  快速排序是目前基于比較的內部排序中被認為是最好的方法,當待排序的關鍵字是隨機分布時,快速排序的平均時間最短;  堆排序所需的輔助空間少于快速排序,并且不會出現快速排序可能出現的最壞情況。這兩種排序都是不穩定的。  若要求排序穩定,則可選用歸并排序。但本章介紹的從單個記錄起進行兩兩歸并的 排序算法并不值得提倡,通常可以將它和直接插入排序結合在一起使用。先利用直接插入排序求得較長的有序子文件,然后再兩兩歸并之。因為直接插入排序是穩定 的,所以改進后的歸并排序仍是穩定的。4)在基于比較的排序方法中,每次比較兩個關鍵字的大小之后,僅僅出現兩種可能的轉移,因此可以用一棵二叉樹來描述比較判定過程。  當文件的n個關鍵字隨機分布時,任何借助于"比較"的排序算法,至少需要O(nlgn)的時間。  箱排序和基數排序只需一步就會引起m種可能的轉移,即把一個記錄裝入m個箱子之一,因此在一般情況下,箱排序和基數排序可能在O(n)時間內完成對n個 記錄的排序。但是,箱排序和基數排序只適用于像字符串和整數這類有明顯結構特征的關鍵字,而當關鍵字的取值范圍屬于某個無窮集合(例如實數型關鍵字)時, 無法使用箱排序和基數排序,這時只有借助于"比較"的方法來排序。  若n很大,記錄的關鍵字位數較少且可以分解時,采用基數排序較好。雖然桶排序對關鍵字的結構無要求,但它也只有在關鍵字是隨機分布時才能使平均時間達到 線性階,否則為平方階。同時要注意,箱、桶、基數這三種分配排序均假定了關鍵字若為數字時,則其值均是非負的,否則將其映射到箱(桶)號時,又要增加相應 的時間。(5)有的語言(如Fortran,Cobol或Basic等)沒有提供指針及遞歸,導致實現歸并、快速(它們用遞歸實現較簡單)和基數(使用了指針)等排序算法變得復雜。此時可考慮用其它排序。(6)本章給出的排序算法,輸人數據均是存儲在一個向量中。當記錄的規模較大時,為避免耗費大量的時間去移動記錄,可以用鏈表作為存儲結構。譬如插入排 序、歸并排序、基數排序都易于在鏈表上實現,使之減少記錄的移動次數。但有的排序方法,如快速排序和堆排序,在鏈表上卻難于實現,在這種情況下,可以提取 關鍵字建立索引表,然后對索引表進行排序。然而更為簡單的方法是:引人一個整型向量t作為輔助表,排序前令t[i]=i(0≤i<n),若排序算法 中要求交換R[i]和R[j],則只需交換t[i]和t[j]即可;排序結束后,向量t就指示了記錄之間的順序關系: R[t[0]].key≤R[t[1]].key≤…≤R[t[n-1]].key 若要求最終結果是: R[0].key≤R[1].key≤…≤R[n-1].key則可以在排序結束后,再按輔助表所規定的次序重排各記錄,完成這種重排的時間是O(n)。

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

推薦閱讀更多精彩內容