2022-10-04 --复杂度和简单排序算法

Summary:

  1. 时间复杂度基本定义
  2. 排序算法
    2.1选择排序 selection
    2.2 冒泡排序 bubble -- 补充1
    2.3 插入排序 insertion
    2.4 归并排序 merge --补充2,4
    2.5 快速排序 quick
    2.6 堆排序 heap --补充5
    2.7 桶排序 bucket
  3. 排序算法的稳定性及汇总

补充:

  1. 异或运算exclusive OR (xor)
  2. 二分查找
  3. 对数器
  4. 递归及master公式
  5. java比较器 comparator

1. 时间复杂度

  1. 常数操作:与数据量无关,每次都是固定时间内完成的操作,e.g. +/-/*/÷, 位运算,Array[i]。但是Linkedlist.get(i) 就不是,因为要遍历,数据量不同,遍历时间不同。
  2. 时间复杂度:常数操作数量的指标-- Big O()
  3. 忽略低阶,只要最高阶项,且忽略最高阶项系数
  4. 评价算法好坏,先看时间复杂度指标,再分析不同数据样本下的实际运行时间(常数项时间)
  5. 算法流程按最差情况估计时间复杂度

2. 排序算法

2.1 选择排序 Selection sort:时间复杂度O(N^2), 额外空间复杂度O(1)

第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零。

public static void selectionSort(int[] arr){
    if(arr == null || arr.length < 2){
        return;
    }
    for(i = 0; i < arr.length; i++){
        int minIndex = I;
        for (int j = 0; j < arr.length; j++){
            if(arr[j] < arr[minIndex]){
                minIndex = j;
            }
        }
        swap(arr, i, minIndex);
    }
}

public static void swap(int[] arr, int i, int j){
    int tmp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}
  1. 时间复杂度O(N^2):
  • 对每个数的for loop: N+N-1+N-2+N-3+....+1 -> O(N^2)
  • 每一个数都要跟后面所有的数比: N+N-1+N-2+N-3+....+1 -> O (N^2)
  • 交换: 1+1+1+1...+1 -> O(N)
  1. 额外空间复杂度O(1)
  • 创建了新变量i,j,minIndex

2.2冒泡排序 Bubble sort:时间复杂度O(N^2), 额外空间复杂度O(1)

比较相邻的元素。如果第一个比第二个大,就交换他们两个。对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。针对所有的元素重复以上的步骤,除了最后一个。持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

public static void bubbleSort(int[] arr){
    if(arr == null || arr.length < 2){
        return;
    }
    for(int i=0;i<arr.length;i++){//外层循环控制排序趟数
      for(int j=0;j<arr.length-i;j++){//内层循环控制每一趟排序多少次
        if(arr[j]>arr[j+1]){
                swap(arr, i, i+1)
        }
      }
    } 
}
public static void swap(int[] arr, int i, int j){
    //不需要额外空间,交换值
    // 前提,a,b在内存中不同, 内存相同会把此处的值变成0,因为N^N=0
    //int a = 甲 int b = 乙
    //array中使用,i和j不能是同一位置
    arr[i] = arr[i] ^ arr[j];// a = a^b; a = 甲^乙, b= 乙
    arr[j] = arr[i] ^ arr[j];// b = a^b; a = 甲^乙, b= 甲^乙^乙 = 甲^(乙^乙) = 甲^0 = 甲
    arr[i] = arr[i] ^ arr[j];// a = a^b; a = 甲^乙^甲= 甲^甲^乙 = 0^乙= 乙, b= 甲
}

2.3插入排序 Insertion sort:时间复杂度O(N^2), 额外空间复杂度O(1)

将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面)

public static void insertSort(int[] arr){
    if (arr == null || arr.length < 2){
        return;
    }
    for (int i = 1; i < arr.length; i++){// 0~i 范围有序
        for (int j = i-1; j > = 0 && arr[j] > arr[j+1]; j--){//当前数往左换到不能再换为止,小的在左
            temp = arr[j];
            arr[j] = arr[j+1];
            arr[j+1] = temp;
            
        }
        
    }
}

2.4 归并排序 Merge sort : 时间复杂度O(N logN), 额外空间复杂度O(N)

归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。作为一种典型的分而治之思想的算法应用,归并排序的实现由两种方法:
自上而下的递归(所有递归的方法都可以用迭代重写,所以就有了第 2 种方法)或自下而上的迭代

  1. 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;
  2. 设定两个指针,最初位置分别为两个已经排序序列的起始位置
  3. 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
  4. 重复步骤 3 直到某一指针达到序列尾
  5. 将另一序列剩下的所有元素直接复制到合并序列尾
public static void mergeSort(int[] arr, int L, int R){
    if(L == R){
        return;
    }
    int mid = L + ((R - L) >> 1);
    mergeSort(arr, L, mid);//左边有序
    mergeSory(arr, mid + 1; R);//右边有序
    merge(arr, L, mid, R);//整体有序
}

public static void merge(int[] arr, int L, int M, int R){
    int[] help = new int[R - L + 1];//等规模空间
    int i = 0;
    int p1 = L;
    int p2 = M + 1;
    while (p1 <= M && p2 <= R){//判断p1,p2是否越界
        help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
    }
    while (p1 <= M){//p1没越界
        help[i++] = arr[p1++];
    }
    while (p2 <= R){//p2没越界
        help[i++] = arr[p2++];
    }
    //两个while只能中一个,p1和p2只能有一个没越界
    for(i = 0; i < help.length; i++){//排序好拷贝回原array
        arr[L + i] = help [i];
    }
}

应用master公式:
T(N) = 2*T(N/2) + O (N)
注:merge时间复杂度 O(N)
a = 2, b = 2, d =1
时间复杂度O(N logN)

  • 好处:没有浪费比较行为,左右两边变成了整体有序的array,之后再合成更大的array

2.4.1小和问题:

在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组的小和。求一个数组的小和。例如:
对于数组[1,3,4,2,5]
1左边比1小的数,没有;
3左边比3小的数,1;
4左边比4小的数,1、3;
2左边比2小的数,1;
5左边比5小的数,1、3、4、2;
所以小和为1+1+3+1+1+3+4+2=16


思路:逆向思维--> 一个数右边有几个数比它大。使用归并排序

public static int smallSum(int[] arr){
    if(arr == null || arr.length < 2){
        return 0;
    }
    return process(arr, 0, arr.length - 1);
}

//arr[L...R] 既要排好序,也要求小和
public static int process(int[] arr, int L, int R){
    if (L == R){
        return 0;
    }
    int mid = L + ((R - L) >> 1);
    //return左边小和+右边小和+合并时小和
    return process(arr, L, mid) + process(arr, mid + 1, R) + merge (arr, L, mid, R);
}

public static int merge(int[] arr, int L, int M, int R){
    int[] help = new int[R - L + 1];
    int i = 0;
    int p1 = L;
    int p2 = M + 1;
    int res = 0;
    while (p1 <= M && p2 <= R){
        //只有当左边比右边小时,才放左边,这样才能准备计算出右边有几个数比左边大,计算左边产生的小和
      // 右组的数永远不产生小和
        res += arr[p1] < arr[p2] ? (R - p2 + 1) * arr[p1] : 0;
        help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
    }
    while (p1 <= M){//p1没越界
        help[i++] = arr[p1++];
    }
    while (p2 <= R){//p2没越界
        help[i++] = arr[p2++];
    }
    //两个while只能中一个,p1和p2只能有一个没越界
    for(i = 0; i < help.length; i++){//排序好拷贝回原array
        arr[L + i] = help [i];
    }
    return res;
}

2.4.2 逆序对:

在一个array中,左边的数如果比右边的数大,则这两个数构成一个逆序对,求逆序对数量
举个例子(前提假设升序为有序),数组[1,2,3],逆序对的数目为零;而数组[3,2,1]的逆序对就是3,分别为(3,2)、(3,1)和(2,1)

2.5 快速排序 Quick sort :时间复杂度O(N^2)

  1. 从数列中挑出一个元素,称为 "基准"(pivot);
  2. 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作
  3. 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序

2.5.1 快排 1.0: O(N^2)

选最末的数为pivot,分两个区域,一个是小于这个数的,一个是大于这个数的,每次完成后把最末的数和大于区域的第一个数交换位置。之后对两个区域继续相同操作

2.5.2 快排 2.0: O(N^2)

选最末的数为pivot,分三个区域,一个是小于这个数的,一个是大于这个数的,一个是等于这个数的,每次完成后把最末的数和大于区域的第一个数交换位置。之后对两个区域继续相同操作

  • 时间复杂度最差O(N^2):123456789,本身有序,pivot选最后,每次partition只能搞定一个位置

2.5.3 快排3.0: 时间复杂O(N log N) ,额外空间复杂O(log N)

随机选一个数为pivot,分三个区域,一个是小于这个数的,一个是大于这个数的,一个是等于这个数的,每次完成后把最末的数和大于区域的第一个数交换位置。之后对两个区域继续相同操作
时间复杂度:O(N * log N)

public static void quickSort(int[] arr){
    if(arr == null || arr.length < 2){
        return;
    }
    quickSort(arr, 0, arr.length -1);
}

public static void quickSort(int[] arr, int L, int R){
    if (L < R){
        swap (arr, L+(int)(Math.random() * (R - L +1)), R)//随机选一个数,并把它放在最右边
        int[] p = partition(arr, L, R);//返回array长度一定为2,表示划分值等于此数的左边界和右边界
        quickSort(arr, L, p[0] - 1);//< 区
        quickSorr(arr, p[1] + 1, R);//> 区
        
    }
}
//处理 arr[1...R]函数
//默认用arr[R] 划分,为三个区域,<arr[R] , ==arr[R], > arr[R]
//返回等于区(左边界,右边界),长度为2的数组res,res[0]等于区左边界,res[1]等于区右边界
public static int[] partition(int[] arr, int L, int R){
    int less = L - 1;//<区右边界
    int more = R;//>区左边界
    while (L < more){//L表示当前数的位置
        if(arr[L] < arr[R]){//arr[R] 是pivot
            swap(arr,++less,L++);
        }else if(arr[L] > arr[R]){
            swap(arr,--more,L);
        }else{
            L++;
        }
    }
    swap(arr, more, R);
    return new int[] {less + 1, more};
}

public static void swap(int[] arr, int i, int j){
    int tmp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}

2.5.4 荷兰国旗问题

2.6 堆排序 heap sort: 时间复杂度O(N log N),额外空间复杂度O(1)

  1. 把所有数变成max-heap
  2. 把第一个数(一定是最大值)根最后一个数交换
  3. heapsize --,这个数与heap断链
  4. 重复1-3 直到排序完成

public static void heapSort(int[] arr){
    if(arr == null || arr.length < 2){
        return;
    }
    for (int i = 0; i < arr.length; i ++){//O(N)
        heapInsert(arr,i)//O(logN)
    }//把数组排列成max-heap
    int heapSize = arr.length;
    swap(arr, 0, --heapSize);
    while(heapSize > 0){
        heapify(arr, 0, heapSize);//O(logN)
        swap(arr, 0, --heapSize);//O(1)
    }
}

优化:
在数组转max-heap时,原来是自上而下,使用heap insert。优化为先把叶节点变成max-heap,然后自右向左,自下而上。时间复杂度O(N)

for (int i = arr.length -1; i >= 0; i --){//O(N)
        heapify(arr, i, arr.length);
    }

2.6.1 已知一个几乎有序的数组,几乎有序是指,如果把数组排好顺序的话,每个元素移动的距离可以不超过k,并且k相对于数组来说比较小。请选择一个合适的排序算法针对这个数据进行排序。

解题思路:选定前k个数,做小根堆排序,第一个数一定最小,pop第一个,加入K+1的数继续小根堆排序,直到最后。时间复杂度O(N log K)

priorityQueue<Integer> heap = new Priority Queue<>();// min-heap in java

注意⚠️:在java中,优先级结构就是小根堆,可以自动由小到大排序。可以将他看作黑盒,作用就是排序,并返回最小值。但不能高效地改变堆中的值(遍历),所以有时需要手写堆(heap insert, heapify),以便高效查询和修改

public void sortedArrDistanceLessK(int[] arr, int k){
    //默认min-heap
    priorityQueue<Integer> heap = new Priority Queue<>();
    for(int index = 0; index <= Math.min(arr.length, k); index++){
        heap.add(arr[index]);
    }
    for(int i = 0; index < arr,length; index++){
        heap.add(arr[index]);
        arr[i] = heap.poll();
    }
    while(!=heap.isEmpty()){
        arr[i++] = heap.poll();
    }
}

2.7桶排序 bucket sort

不基于比较的排序(计数排序)--需要根据数据状况定制

2.8 基数排序radix sort

3. 排序算法的稳定性及其总结

  1. 稳定性:值相同的元素保持排序完成后相对次序不变
    • 稳定:冒泡/插入/归并/桶/基数
    • 不稳定:选择/快速/堆
  2. 总结:
时间复杂 空间复杂 稳定性
选择 O(N^2) O(1) No
冒泡 O(N^2) O(1) Yes
插入 O(N^2) O(1) Yes
归并 O(N logN) O(N) Yes
快排 O(N logN) O(logN) No
O(N logN) O(1) No
  • 一般选快排,有空间限制选堆,有稳定性要求选归并
  • 基于比较的排序,时间复杂度无法低于O(N logN)
  • 时间复杂度O(N logN),空间复杂度不能小于O(N)且稳定

坑⚠️:
1. 归并可以把空间复杂度降为O(1)-->"归并排序 内部缓存法",但是很难且不稳定。不如堆
2. "原地归并排序",时间复杂升为O(N^2)
3. “01 stable sort”,稳定快排但很难,无意义
4. 所有的改进都不重要
5. 题目:奇数放数组左边,偶数放数组右边,还要原始的相对次序不变,时间复杂O(N^2),空间复杂(1)--> 跟快排评判标准一样(0,1标准),但做不到稳定

  1. 工程上对排序改进:
    1. 综合排序:充分利用O(N logN)排得快 和O(N^2)空间小的各自优势
      2.稳定性

补充1:异或运算exclusive OR (xor)

  1. basic: 1^1 = 0, 1^0 =1, 0^0=0
  2. 可以看作不进位相加
  3. 0^N = N, N^N = 0
  4. 满足交换结合率:a^b = b^a, (ab)c = a(bc)
  5. abcd....z 顺序不影响结果
  • 例题1:int数组中,只有一个数出现奇数次,其他所有数都出现偶数次,怎么找到一个出现奇数次的数?要求时间O(N),额外空间O(1)
public static void findOddnumber(int[] arr){
    int eor = 0;
    for(int cur : arr){//遍历array
        eor = eor ^ cur;//偶数会自己消除,奇数剩下
    }
    //for (int i =0; i< arr.length-1; i++){
   //     eor = eor ^ arr[i];}
    Ststem.out.println(eor);
}
  • 例题2: int数组中,有两个数出现奇数次,其他所有数都出现偶数次,怎么找到两个出现奇数次的数?
public static void findOddnumber2(int[] arr){
    int eor = 0;
    for(int cur : arr){
        eor = eor ^ cur;//偶数会自己消除,奇数剩下
    }
    //eor = a^b
    // eor != 0, 因为a!=b
    //eor 必有一个位置是1
    int rightOne = eor & (~eor+1)// 提取一个非零数最右边的1
    int onlyone = 0;
    //用这一位的1跟所有元素异或,偶数全都消除,只剩下a 或b。因为a,b这一位一定是不同的
    for (int cur:arr){
        if((cur & rightOne) == 0){
            onlyOne = onlyOne ^cur; // a or b
        }
    }
    Ststem.out.println(onlyOne + " "+ (eor ^ onlyOne));
}

补充2:二分查找:

1.有序数组中,查找某个数是否存在:时间复杂度O(logN)
2.在有序数组中,找>= 某个数最左侧的位置:时间复杂度O(logN)
3.局部最小值:无序array,相邻数一定不想等,求局部最小值

补充3:对数器:

1.通过用大量测试数据来验证算法是否正确的一种方式

  1. java中:
    Math.random() [0,1)所有小数,等概率返回一个
    Math.random()*N [0,N)所有小数,等概率返回一个
    (int)(Math.random()*N) [0,N-1]所有整数,等概率返回一个
  2. 生成随机array,可以控制长度和最大值
public static int[] generateRandomArray(int maxSize, int maxValue){
    int[] arr = new int[(int)((maxSize + 1) * Math.random())];
    for (int i = 0; i< arr.length; i++){
        arr[i] = (int)((maxValue + 1) * Math.random())-(int)(maxValue * Math.random());
    }
    return arr;
}
  1. for testing
public static void main (String[] args){
    int testTime = ;
    int maxSize = ;
    int maxValue = ;
    boolean succeed = true;
    for (int i = 0; i < testTime; i++){
        int[] arr1 = generateRandomArray(maxSize, maxValue);
        int[] arr2 = copyArray(arr1);
        //method1
        //method2
        if(!isEqual(arr1,arr2)){
            //System.out.println(arr1);
            //System.out.println(arr2);
            succeed = false;
            break;
        }
    }
    System.out.println(succeed? "succeed": "failed")
}

补充4: 递归及master公式:

1.求arr[L...R]范围中求最大值

public static int getMax(int[] arr){
    return recursion(arr, 0, arr.length -1);
}

//arr[L...R]范围中求最大值
public static int recursion(int[] arr, int L, int R){
    if(L == R){
        return arr[L];
    }
    int mid = L + ((R - L) >> 1); //求中点: `mid = (L + R) / 2` 内存有可能溢出,改为 `mid = L + (R - L)/2` 优化为 `mid = L +((R - L) >> 1)` 右移一位,相当于除2
    int leftMax = recursion(arr, L, mid);
    int rightMax = recursion (arr, mid + 1, R);
    return Math.max (leftMax, rightMax);
}
recursion 示意图
  1. 递归行为时间复杂度的估算:
    master公式: 子问题规模相等
    T(N) = a*T(N/b) + O (N^d)
  • a = 子问题调用次数
  • N/b = 子问题数据规模占总规模的多少(每次递归要相等)
  • O (N^d) = 分解合并的时间复杂度
    解:
  1. logb a < d, 时间复杂度为O(N^d)
  2. logb a > d, 时间复杂度为O(N^(logb a))
  3. logb a = d, 时间复杂度为O((N^d)*log N)

补充5 :堆 heap

  1. 完全二叉树结构:满的二叉树,或从左到右正在变满的二叉树(符合BFS遍历顺序)
  2. array转完全二叉树:
    • arr[i] 左孩子:2 * i + 1
    • arr[i] 右孩子:2 * i + 2
    • arr[i] 父:int((i-1)/2)
      array转完全二叉树
  3. 大根堆和小根堆
    • 大根堆max-heap:父节点的值大于或等于子节点的值
    • 小根堆min-heap: 父节点的值小于或等于子节点的值
  4. heap insert for max-heap
    时间复杂: O(log N)
public static void heapInsert(int[] arr, int index){
    while (arr[index] > arr[(index -1)/2]){
        swap(arr,index, index -1)/2);
        index = (index - 1)/2;
    }
}
  1. heapify:
    Pop the root of a max-heap, then move the last node to the top and rearrange the tree from top-to-bottom to be max-heap again. -- 时间复杂O(log N)
public static void heapify(int[] arr, int index, int heapsize){
    int left = index * 2 + 1;//左孩子下标
    while(left < heapsize){//下方还有孩子
        //左右两个孩子,把值大的下表给largest
        int largest = left + 1 < heapsize && arr[left + 1] > arr[left] ?
            left + 1 : left;
        //较大的孩子和父亲比,把值大的下表给largest
        int largest = arr[largest] > arr[index] ? largest : index;
        if (largest == index){
            break;
        }
        swap(arr, largest, index);
        index = largest;
        left = index * 2 + 1;
    }
}

补充6:java比较器 comparator

  1. 实质是重载比较运算符(自己定义比较方法)
  2. 应用在特殊标准的排序上
  3. 应用在根据特殊标准排序的结构上
  4. 使用方法:Array.sort(arr, comparator)
    • 返回负数时,第一参数排前面
    • 返回正数时,第二参数排前面
    • 返回0 时,谁在前面无所谓
  5. 大根堆比较器:
public static class Acomp implements Comparator<Integer>{
    public int compare(Integer arg0, Integer arg1){
        return arg1 - arg0;
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,123评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,031评论 2 384
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,723评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,357评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,412评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,760评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,904评论 3 405
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,672评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,118评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,456评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,599评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,264评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,857评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,731评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,956评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,286评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,465评论 2 348

推荐阅读更多精彩内容