18. Interview-Data Structure

1 栈

栈,操作受限的线性表,FILO,栈顶插入和删除,只有两种操作,入栈Push(插入)、出栈POP(删除)。

2 队列

队列,操作受限的线性表,FIFO,队尾入队(插入),队首出队(删除)。

队列

3 链表

链表跟数组同级别的数据结构,数组遍历效率高,插入删除效率低,链表插入删除效率高、遍历效率低。ArrayList底层实现是数组,LinkedList底层实现是链表。

链表

4 散列表/哈希表

4.1 hash定义

  • 哈希表又叫散列表或者Hash table,它通过把关键码值key通过一个函数f(key)映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数/哈希函数,存放记录的数组叫做哈希表/散列表/hashtable。
  • 任意key经过哈希函数映射到数组中的任何一个地址的概率相等,这样的散列函数为均匀散列函数。
  • 存在k1不等于k2,使得f(key1)=f(key2),就叫哈希冲突。哈希冲突理论上不可能完全消除,但是应该尽量避免。

4.2 为什么理想情况下hash表的时间复杂度是O(1)?

  • hash表物理存储是个数组,极端情况下,如果所有key都发生了哈希冲突,则数组退化成链表,时间复杂度为O(n)。
  • 如果不考虑哈希冲突,key可以通过哈希函数快速定位到数组下标找到记录value,时间复杂度为O(1)。

4.3 hash函数&hashcode

  • hash函数(哈希),也叫散列/杂凑函数,将任意长度的输入转换为固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,散列值的空间通常小于输入空间,不同的输入可能得到相同的输出。

  • hash函数的基础是hash算法,hash算法也可以认为是一种思想,没有固定的公式,只要符合散列思想的算法都可以成为hash算法,hash算法很难找到逆向规律,可以提高空间利用率,提高数据查询效率,常用于数字签名。

  • hashcode,哈希码,Java的Object.hashCode()记录在Markword中,Hotspot虚拟机由xor-shift算法(异或和移位)实现,用于哈希表的查找

4.4 构造hash函数的方法

  • 直接定址法

    • H(key) = a·key + b,其中a和b为常数,这种线性散列函数也叫做自身函数
    • 此法仅适合于:地址集合的大小 = = 关键字集合的大小,其中a和b为常数。
    • 实际生活中,关键字的元素很少是连续的。用该方法产生的哈希表会造成空间大量的浪费,因此这种方法适应性并不强。
  • 数字分析法

    • 因此数字分析法就是找出数字的规律,尽可能利用这些数据来构造冲突几率较低的散列地址。
    • 此法适于:能预先估计出全体关键字的每一位上各种数字出现的频度。
  • 平方取中法

    • 因为这种方法的原理是通过取平方扩大差别,平方值的中间几位和这个数的每一位都相关,则对不同的关键字得到的哈希函数值不易产生冲突,由此产生的哈希地址也较为均匀。
    • 此法适于:关键字中的每一位都有某些数字重复出现频度很高的现象。
  • 折叠法

    • 将关键字分割成位数相同的几部分(最后一部分的位数可以不同),然后取这几部分的叠加和(舍去进位),这方法称为折叠法。
    • 这种方法适用于关键字位数较多,而且关键字中每一位上数字分布大致均匀的情况。
    • 数位叠加可以有移位叠加和间界叠加两种方法:
      • 移位叠加是将分割后的每一部分的最低位对齐,然后相加;
      • 间界叠加是从一端向另一端沿分割界来回折叠,然后对齐相加。
  • 随机数法

    • 设定哈希函数为:H(key) = Random(key)其中,Random 为伪随机函数
    • 此法适于:对长度不等的关键字构造哈希函数。
  • 除留余数法

    • 取关键字被某个不大于散列表表长m的数p除后所得的余数为散列地址。即 H(key) = key MOD p,p<=m。不仅可以对关键字直接取模,也可在折叠、平方取中等运算之后取模。对p的选择很重要,除留余数法的模p取不大于表长且最接近表长m素数时效果最好,且p最好取1.1n~1.7n之间的一个素数(n为存在的数据元素个数),若p选的不好,容易产生同义词。
  • 基数转换法

    • 将十进制数X看作其他进制,比如十三进制,再按照十三进制数转换成十进制数,提取其中若干为作为X的哈希值。一般取大于原来基数的数作为转换的基数,并且两个基数应该是互素的。
    • 为了获得良好的哈希函数,可以将几种方法联合起来使用,比如先变基,再折叠或平方取中等等,只要散列均匀,就可以随意拼凑。
  • 随机乘数法

    • 亦称为“乘余取整法”。随机乘数法使用一个随机实数f,0≤f<1,乘积fk的分数部分在0~1之间,用这个分数部分的值与n(哈希表的长度)相乘,乘积的整数部分就是对应的哈希值,显然这个哈希值落在0~n-1之间。其表达公式为:Hash(k)=「n(fk%1)」其中“fk%1”表示fk 的小数部分,即fk%1=fk-「fk」
    • 此方法的优点是对n的选择不很关键。通常若地址空间为p位就是选n=2p.Knuth对常数f的取法做了仔细的研究,他认为f取任何值都可以,但某些值效果更好。如f=(-1)/2=0.6180329...比较理想。
  • 字符串数值哈希法

    • 把字符串的前10个字符的ASCⅡ值之和对N取摸作为Hash地址,只要N较小,Hash地址将较均匀分布[0,N]区间内。
  • 旋转法

    • 旋转法是将数据的键值中进行旋转。旋转法通常并不直接使用在哈希函数上,而是搭配其他哈希函数使用。
  • 减去法

    • 减去法是数据的键值减去一个特定的数值以求得数据存储的位置。

4.5 解决hash冲突的方法

  • 开放定址法(封闭散列)

    • Hi=(H(key) + di) MOD m,i=1,2,…,k(k<=m-1),其中H(key)为散列函数,m为散列表长,di为增量序列,可有下列三种取法:
      • 线性探测再散列:di=1,2,3,…,m-1;
      • 二次探测再散列:di=12,-12,22,-22,⑶2,…,±(k)2,(k<=m/2);
      • 伪随机探测再散列:di=伪随机数序列。
    • 优点:
      • 记录更容易进行序列化(serialize)操作
      • 如果记录总数可以预知,可以创建完美哈希函数,此时处理数据的效率是非常高的
    • 缺点:
      • 存储记录的数目不能超过桶数组的长度,如果超过就需要扩容,而扩容会导致某次操作的时间成本飙升,这在实时或者交互式应用中可能会是一个严重的缺陷;
      • 使用探测序列,有可能其计算的时间成本过高,导致哈希表的处理性能降低;
      • 由于记录是存放在桶数组中的,而桶数组必然存在空槽,所以当记录本身尺寸(size)很大并且记录总数规模很大时,空槽占用的空间会导致明显的内存浪费;
      • 删除记录时,比较麻烦。比如需要删除记录a,记录b是在a之后插入桶数组的,但是和记录a有冲突,是通过探测序列再次跳转找到的地址,所以如果直接删除a,a的位置变为空槽,而空槽是查询记录失败的终止条件,这样会导致记录b在a的位置重新插入数据前不可见,所以不能直接删除a,而是设置删除标记。这就需要额外的空间和操作。
  • 再散列法

    • Hi=RHi(key),i=1,2,…,k RHi均是不同的散列函数,即在同义词产生地址冲突时计算另一个散列函数地址,直到冲突不再发生,这种方法不易产生“聚集”,但增加了计算时间。
  • 链地址法/拉链法(开放散列)

    • 链地址法的基本思想是:每个哈希表节点都有一个next指针,多个哈希表节点可以用next指针构成一个单向链表,被分配到同一个索引上的多个节点可以用这个单向链表连接起来。
    • 这种方法的基本思想是将所有哈希地址为i的元素构成一个称为同义词链的单链表,并将单链表的头指针存在哈希表的第i个单元中,因而查找、插入和删除主要在同义词链中进行。链地址法适用于经常进行插入和删除的情况。
    • 优点:
      • 对于记录总数频繁可变的情况,处理的比较好(也就是避免了动态调整的开销);
      • 由于记录存储在结点中,而结点是动态分配,不会造成内存的浪费,所以尤其适合那种记录本身尺寸(size)很大的情况,因为此时指针的开销可以忽略不计了;
      • 删除记录时,比较方便,直接通过指针操作即可。
    • 缺点:
      • 存储的记录是随机分布在内存中的,这样在查询记录时,相比结构紧凑的数据类型(比如数组),哈希表的跳转访问会带来额外的时间开销;
      • 如果所有的 key-value 对是可以提前预知,并之后不会发生变化时(即不允许插入和删除),可以人为创建一个不会产生冲突的完美哈希函数(perfect hash function),此时封闭散列的性能将远高于开放散列;
      • 由于使用指针,记录不容易进行序列化(serialize)操作。
  • 建立一个公共溢出区

    • 这种方法的基本思想是:将哈希表分为基本表和溢出表两部分,凡是和基本表发生冲突的元素,一律填入溢出表。

4.6 hash表的查找性能

查找过程中,关键码的比较次数,取决于产生冲突的多少,产生的冲突少,查找效率就高,产生的冲突多,查找效率就低。因此,影响产生冲突多少的因素,也就是影响查找效率的因素。影响产生冲突多少有以下三个因素:

  • 散列函数是否均匀;
  • 处理冲突的方法;
  • 散列表的装填因子。
    • 散列表的装填因子定义为:α= 填入表中的元素个数 / 散列表的长度
    • α是散列表装满程度的标志因子。由于表长是定值,α与“填入表中的元素个数”成正比,所以,α越大,填入表中的元素较多,产生冲突的可能性就越大;α越小,填入表中的元素较少,产生冲突的可能性就越小。

4.7 著名的hash算法

MD5 和 SHA-1 可以说是目前应用最广泛的Hash算法,而它们都是以 MD4 为基础设计的。

  • MD4

MD4(RFC 1320)是 MIT 的 Ronald L. Rivest 在 1990 年设计的,MD 是 Message Digest 的缩写。它适用在32位字长的处理器上用高速软件实现--它是基于 32 位操作数的位操作来实现的。

  • MD5

MD5(RFC 1321)是 Rivest 于1991年对MD4的改进版本。它对输入仍以512位分组,其输出是4个32位字的级联,与 MD4 相同。MD5比MD4来得复杂,并且速度较之要慢一点,但更安全,在抗分析和抗差分方面表现更好

  • SHA-1 及其他

SHA1是由NIST NSA设计为同DSA一起使用的,它对长度小于264的输入,产生长度为160bit的散列值,因此抗穷举(brute-force)性更好。SHA-1 设计时基于和MD4相同原理,并且模仿了该算法。

4.8 hash算法在信息安全方面的应用

  • 文件校验

    • 我们比较熟悉的校验算法有奇偶校验和CRC校验,这2种校验并没有抗数据篡改的能力,它们一定程度上能检测出数据传输中的信道误码,但却不能防止对数据的恶意破坏。
    • MD5 Hash算法的"数字指纹"特性,使它成为目前应用最广泛的一种文件完整性校验和(Checksum)算法,不少Unix系统有提供计算md5 checksum的命令。
  • 数字签名

    • Hash 算法也是现代密码体系中的一个重要组成部分。由于非对称算法的运算速度较慢,所以在数字签名协议中,单向散列函数扮演了一个重要的角色。对 Hash 值,又称"数字摘要"进行数字签名,在统计上可以认为与对文件本身进行数字签名是等效的。而且这样的协议还有其他的优点。
  • 鉴权协议

    • 如下的鉴权协议又被称作挑战--认证模式:在传输信道是可被侦听,但不可被篡改的情况下,这是一种简单而安全的方法。

5 二叉树

  • 每个节点最多只有2个子树的树结构叫二叉树。二叉树有五种基本形态。
  • 二叉树4大性质:
    • 二叉树的第i层上的结点数目最多为2(i-1),i>=1
    • 深度为k的二叉树至多有2k-1个结点,k>=1
    • 包含n个结点的二叉树的高度至少为log2(n+1)
    • 度为0的结点数为n0,度为2的结点数为n2,则n0=n2+1
二叉树
二叉树的五种基本形态

6 BST树

  • BST树,Binary Search Tree,二叉查找树、二叉搜索树、排序二叉树。

    • 左子树所有结点值小于根结点值,右子树所有结点值大于根结点值;
    • 任意结点的左右子树都是BST树;
    • 没有键值相等的结点。
  • BST插入操作(从根节点开始递归):

    • 新插入的节点与当前节点比较,相同则说明已存在不用重复插入;
    • 新插入的节点 < 当前节点,则到当前节点左子树比较,直到插入成功;
    • 新插入的节点 > 当前节点,则到当前节点右子树比较,直到插入成功。
  • BST删除操作(从根节点开始递归):

    • 待删除节点无子节点,则直接删除;
    • 待删除节点只有一个子节点,则直接删除;
    • 待删除节点有两个子节点,则先找到待删除节点的替代节点(右子树的最小节点),然后替换节点,再删除替换节点。
  • BST查询操作(递归遍历):

    • 深度优先遍历
      • 前序遍历:最常见,根左右
      • 中序遍历:左根右,整棵BST树采用中序遍历即从小到大排序
      • 后序遍历:左右根
    • 广度优先遍历/层序遍历:从上层到下层,每层从左到右
BST删除任意节点-后继
BST删除任意节点-前驱
BST前序遍历-根左右
BST中序遍历-左根右
BST后序遍历-左右根

7 AVL树

  • AVL树是平衡的BST,在BST的特点上还有如下特点:

    • AVL树的左右子树高度之差的绝对值不超过1;
    • AVL树的左右子树都是AVL树
    • AVL树任意节点的平衡因子只能是-1/0/1,平衡因子=右子树高度-左子树高度
  • AVL树经历插入或删除操作后,如果平衡条件被破坏,需要进行旋转操作来恢复平衡。

平衡因子=右子树高度-左子树高度

8 红黑树

  • 红黑树是自平衡的BST。红黑树有5大特点:

    • 节点有红色或黑色;
    • 根节点是黑色;
    • 叶子结点是黑色;
    • 每个红色节点的子节点都是黑色;
    • 从任意节点到其叶子结点的所有路径都包含相同数目的黑色节点。
  • 红黑树通过变色、左旋、右旋来保持平衡,任何不平衡都会在三次旋转之内解决。

  • 红黑树通过为节点增加颜色换取增删节点时候旋转次数的降低,AVL树旋转次数比红黑树要多,红黑树的插入效率更高。

  • 红黑树的左旋操作(左比右低)

左旋
  • 红黑树的右旋操作(左比右高)
右旋
  • 红黑树增加节点:

    • 按照BST的方式,将节点插入;

    • 将插入的节点着色为红色

      • 插入的节点是根节点:直接涂为黑色
      • 插入的节点的父节点是黑色:什么也不做
      • 插入的节点的父节点是红色:说明插入节点存在非空祖父节点和叔叔节点,分3种情况讨论
        • 红黑树-增加节点
    • 一系列旋转和着色,使之成为一颗红黑树。

  • 红黑树删除节点:

    • 按照BST的方式,将节点删除;
    • 一系列旋转和着色,使之成为一颗红黑树:
      • 被删除节点是红+黑:直接涂为黑色;
      • 被删除节点是黑+黑,且是根节点:什么都不做
      • 被删除节点是黑+黑,且不是根节点:分4种情况讨论。
        • 红黑树-删除节点

9 B Tree

  • 数据库索引文件存储在磁盘上,当数据量比较大的时候,索引文件很大,不能一次性装载到内存,只能每次装载磁盘页,磁盘页里面就是索引树的节点。
  • 每次磁盘IO就是将树的节点装载到磁盘页加载到内存,BST在最坏情况下,磁盘IO次数等于索引树的高度。
  • 为了减少磁盘IO次数,要把树从"瘦高"变得"矮胖",BST是二叉查找树,B树是多路平衡查找树。B树的比较次数不比BST少,但是相对磁盘IO的速度,内存中比较耗时几乎可以忽略,只要树的高度足够低,磁盘IO次数足够小,就可以提升查找性能。
  • B树每个节点最多包含m个孩子,m称为B树的阶,m的大小取决于磁盘页的大小。一个m阶的B树具有如下几个特征:
    • 根节点至少有两个孩子;
    • 每个叶子结点都包含k-1个元素,其中k取值[m/2,m];
    • 所有的叶子结点都处于同一层;
    • 每个节点中的元素从小到大排列;
    • 每个中间节点都包含k-1个元素和k个孩子,其中k取值[m/2,m];

10 B+ Tree

  • B+树是B树的变体,比B树有更高的查询性能,在B树基础上还有如下特点:
    • 非叶子结点不保存数据只用于索引,所有数据都保存在叶子结点,叶子结点之间指针相连,叶子结点元素从小到大排序;
    • 有k个子树的中间节点包含有k个元素(B树中是k-1个元素);
  • B+树中间节点只用来索引,所以同样大小的磁盘页可以容纳更多的节点元素,数据量相同情况下,B+树的结构比B-树更加"矮胖",因此查询时IO次数更少,查询效率更高。
  • B+树每次查询必须到叶子结点结束,B树只要匹配到元素即可,因此B+树的查找比B树更稳定。
  • B+树所有叶子节点形成有序链表,便于范围查询。

11 线段树

线段树使用场景-区间染色
线段树是使用场景-区间查询
线段树区间查询时间复杂度
线段树
满二叉树节点数量和层数关系

12 Trie Tree(字典树)

Trie树使用场景
Trie树/字典树/前缀树

13 并查集(Union Find)

并查集使用场景
并查集Union Find

14 SkipList(跳表)

  • SkipList,跳表/跳跃表,是一种空间换时间的算法,是一种随机化的数据结构,跳表的原理比较简单,但是效率和红黑树及AVL树不相上下,开源软件Redis和LevelDB都有使用到跳表。

    • 跳表由很多层结构组成;
    • 每一层都是一个有序的链表;
    • 最底层(Level 1)链表包含所有的元素;
    • 如果一个元素出现在Level i的链表中,则它一定也会出现在Level i下层的链表中;
    • 每个节点都包含两个指针,一个指向同一层链表中的下一个元素,一个指向下面一层的元素。
  • 丢硬币决定K,随机变量K满足参数为p=1/2的几何分布,K的期望值为2层。

  • 跳表的高度等于n次实验中产生的最大K

  • 跳表的空间复杂度:每个元素的期望高度是2,一个大小为n的跳表,其节点数目的期望值是2n

  • 跳表的查询

    • 从top层开始,依次跟链表中元素比较;
    • 待查找元素比当前节点大,同时比链表中最大值小的时候,跳到下一层继续比较,依次进行;
    • 最坏情况会到Level 1层比较,直到查找到所需元素,否则没有此元素。
  • 跳表的插入

    • 先确定待插入元素会插入到第K层,K是丢硬币随机决定的;
    • 然后在第K层的以下各层都插入该元素;
    • 如果K大于跳表的层数,则要添加新的层。
  • 跳表的删除

    • 先在每一层查找到待删除的节点;
    • 然后在每一层使用链表中删除节点的方式删除该节点。

15 位图(bitmap)

  • 位图通常基于数组来实现,数组每个元素存储一个数据,该元素只占一个bit位,用一个bit标识该元素是否存在,可以大大节省内存空间。

  • 位图常用业务场景:

    • 布隆过滤器bloom filter
    • 快速去重
    • 快速排序
    • 快速查询
  • Bitmap实现快速去重

    • 2.5亿个整数中找出不重复的整数的个数,内存空间不足以容纳这2.5亿个整数。 首先,根据“内存空间不
      足以容纳这2.5亿个整数”我们可以快速的联想到Bit-map。
    • 下边关键的问题就是怎么设计我们的Bit-map来表示这2.5亿个数字的状态了。其实这个问题很简单,一个数字的状态只有三种,分别为不存在,只有一个,有重复。因此,我们只需要2bits就可以对一个数字的状态进行存储了,假设我们设定一个数字不存在为00,存在一次01,存在两次及其以上为11。那我们大概需要存储空间几十兆左右。 接下来的任务就是遍历一次这2.5亿个数字,如果对应的状态位为00,则将其变为01;如果对应的状态位为01,则将其变为11;如果为11,,对应的转态位保持不变。
    • 最后,我们将状态位为01的进行统计,就得到了不重复的数字个数,时间复杂度为O(n)。
  • Bitmap实现快速排序

    • 假设我们要对0-7内的5个元素(4,7,2,5,3)排序(这里假设这些元素没有重复),我们就可以采用Bit-map的方法来达到排序的目的。要表示8个数,我们就只需要8个Bit(1Bytes),首先我们开辟1Byte的空间,将这些空间的所有Bit位都置为0, 对应位设置为1:
    • 遍历一遍Bit区域,将该位是一的位的编号输出(2,3,4,5,7),这样就达到了排序的目的,时间复杂度O(n)。
    • 优点: 运算效率高,不需要进行比较和移位;占用内存少,比如N=10000000;只需占用内存为N/8=1250000Byte=1.25M。
    • 缺点: 所有的数据不能重复。即不可对重复的数据进行排序和查找。
  • Bitmap实现快速查询

    • 利用Bit-map也可以进行快速查询,这种情况下对于一个数字只需要一个bit位就可以了,0表示不
      存在,1表示存在。假设上述的题目改为,如何快速判断一个数字是够存在于上述的2.5亿个数字集合中。
    • 首先我们先对所有的数字进行一次遍历,然后将相应的转态位改为1。遍历完以后就是查询,由于我们的Bit-map采取的是连续存储(整型数组形式,一个数组元素对应32bits),我们实际上是采用了一种分桶的思想。一个数组元素可以存储32个状态位,那将待查询的数字除以32,定位到对应的数组元素(桶),然后再求余(%32),就可以定位到相应的状态位。如果为1,则代表改数字存在;否则,该数字不存在。
  • Bitmap实现布隆过滤器(Bloom Filter)

    • Bloom Filter减少误判概率方法:
      • 让Bitmap的空间更大,hash次数更大(一般是8次),空间和准确率上折中;
      • 加一个白名单,专门存储误判的URL或邮箱等数据。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343