数据结构与算法04 -- 单向链表

  • 动态数组存在明显的缺点:
    • 首先动态扩容可能会造成内存空间的大量浪费;
    • 使用链表这种数据结构就可以办到,使用多少内存,就申请多少内存,不会造成内存空间的浪费;下面引出链表这种数据结构;

链表

  • 是一种链式存储的线性表,所有元素的内存地址不一定是连续的;
  • 其结构如下图所示:
Snip20210330_73.png
  • size与first是链表的两个属性,size是链表存储元素的个数;first是链表的首节点;
  • Node是节点模型,每一个节点内部有element与next两个属性,element是数据元素,next当前节点的下一个节点(后继节点);

链表的接口设计

  • 链表的接口与动态数组的接口大致相同;
  • 链表在初始化的时候不需要指定容量大小;
  • 结合Java的继承(extends)接口(interface)抽象(abstract)三大技术对动态数组类YYArrayList与链表类LinkedList进行重新设计:
  • 首先定义一个接口类interface List<E>,内部定义声明YYArrayList与YYSingleLinkedList共用的方法;注意接口只做方法的定义声明,不会去实现方法,方法的实现交给YYArrayList与YYSingleLinkedList;
  • 由于YYArrayList与LinkedList有公共的方法实现,所以定义一个基类AbstrctList<E>,将公共的方法实现封装在基类中,且让基类去实现接口List<E>,但只实现YYArrayList与YYSingleLinkedList公共的方法,这样会报错,为了让基类AbstrctList<E>只实现部分接口List<E>方法(公共部分),且不报错,引出抽象类的技术,使用abstract关键字修饰基类AbstrctList<E>即可;
  • 代码实现如下所示:

【第一部分】:接口类interface List<E>

package com.example.linkedlist.list;

public interface List<E> {

    /**
     * 目标元素不存在,返回的默认下标index
     */
     static final int ELEMENT_NOT_FOUND = -1;

    /**
     * 返回数组元素的数量
     */
    int size();

    /**
     * 往数组添加目标元素
     * @param element 目标元素
     */
     void addObject(E element);

    /**
     * 在指定index位置插入元素
     * @param index
     * @param element
     */
     void insertObjectAtIndex(int index,E element);

    /**
     * 删除指定index位置的元素,且返回被删除的元素
     * @param index
     * @return
     */
     E removeObjectAtIndex(int index);

    /**
     * 清除所有元素
     */
     void clear();

    /**
     * 设置指定index位置的元素,且返回原来的元素
     * @param index 指定下标
     * @param element 新元素
     * @return 原来的元素
     */
     E setObjectAtIndex(int index,E element);

    /**
     * 获取指定index下标的元素
     * @param index 指定下标
     * @return 目标元素
     */
     E objectAtIndex(int index);

    /**
     * 查询元素的index下标
     * @param element
     * @return
     */
     int indexOfObject(E element);

    /**
     * 判断数组是否为空
     */
     boolean isEmpty();

    /**
     * 判断是否包含某个元素
     * @param element 目标元素
     * @return
     */
     boolean containsObject(E element);
}

【第二部分】:基类AbstrctList<E>

package com.example.linkedlist.list;

public abstract class AbstrctList<E> implements List<E>{

    /**
     * 元素的数量
     */
    protected int size;

    /**
     * 返回数组元素的数量
     */
    public int size(){
        return size;
    }

    /**
     * 往数组添加目标元素
     * @param element 目标元素
     */
    public void addObject(E element){
        insertObjectAtIndex(size,element);
    }

    /**
     * 判断数组是否为空
     */
    public boolean isEmpty(){
        return size == 0;
    }

    /**
     * 判断是否包含某个元素
     * @param element 目标元素
     * @return
     */
    public boolean containsObject(E element){
        //判断目标元素的index是否存在
        return indexOfObject(element) != ELEMENT_NOT_FOUND;
    }

    /**
     * index检测
     * @param index
     */
    public void rangeCheck(int index){
        if (index < 0 || index >= size){
            throw  new IndexOutOfBoundsException("Index:" + index + ", Size:" + size);
        }
    }

    /**
     * 针对Insert -- index检测
     * @param index
     */
    public void rangeCheckForInsert(int index){
        if (index < 0 || index > size){
            throw  new IndexOutOfBoundsException("Index:" + index + ", Size:" + size);
        }
    }
}

【第三部分】:动态数组类YYArrayList<E>

package com.example.linkedlist.list;

/**
 * 动态数组
 * @param <E>
 */
public class YYArrayList<E> extends AbstrctList<E>{

    /**
     * 存储所有元素
     */
    private E[] elements;

    /**
     * 存储元素的默认长度
     */
    private static final int DEFAULT_CAPACITY = 2;
    
    public YYArrayList(){
        //构造函数之间的调用 用到this关键字
        this(DEFAULT_CAPACITY);
    }

    /**
     * 自定义构造方法
     * @param capaticy 存储元素的长度
     */
    public YYArrayList(int capaticy){
        capaticy = capaticy < DEFAULT_CAPACITY ? DEFAULT_CAPACITY : capaticy;
        //申请内存空间
        elements = (E[])new Object[capaticy];
    }

    @Override
    public void insertObjectAtIndex(int index, E element) {
        if (element == null) return;
        rangeCheckForInsert(index);
        //检测是否需要进行扩容
        ensureCapacity(size + 1);

        for (int i = size - 1;i >= index;i--){
            elements[i+1] = elements[I];
        }
        elements[index] = element;
        size++;
    }

    @Override
    public E removeObjectAtIndex(int index) {
        rangeCheck(index);
        E old = elements[index];
        for (int i = index + 1;i <= size - 1;i++){
            elements[i-1] = elements[I];
        }
        elements[--size] = null;
        return old;
    }

    @Override
    public void clear() {
        //将数组中对象地址全部清空 -- 对象回收
        //数组没有回收,可循环使用
        for (int i = 0; i < size;i++){
            elements[i] = null;
        }
        size = 0;
    }

    @Override
    public E setObjectAtIndex(int index, E element) {
        rangeCheck(index);
        E old = elements[index];
        elements[index] = element;
        return old;
    }

    @Override
    public E objectAtIndex(int index) {
        rangeCheck(index);
        return elements[index];
    }

    @Override
    public int indexOfObject(E element) {
        if (element == null){
            for (int i = 0;i < size;i++){
                if (elements[i] == null) return I;
            }
        }else {
            for (int i = 0;i < size;i++){
                if (elements[i].equals(element)) return I;
            }
        }
        return ELEMENT_NOT_FOUND;
    }

    @Override
    public String toString() {
        StringBuffer string = new StringBuffer();
        string.append("size=").append(size).append(", [");
        for (int i = 0;i < size;i++){
            string.append(elements[I]);
            if (i != size - 1){
                string.append(", ");
            }
        }
        string.append("]");
        return string.toString();
    }

    /**
     * 保证要有capacity的容量
     * @param capacity
     */
    private void ensureCapacity(int capacity){
        int oldCapacity = elements.length;
        //不需要扩容
        if (oldCapacity >= capacity) return;

        //需要进行数组的扩容,新的容量大小是原来的1.5倍
        int newCapacity = oldCapacity + (oldCapacity >>1);
        //重新申请内存空间
        E[] newElements = (E[])new Object[newCapacity];
        //将原来内存空间上的元素赋值到新开辟的内存空间中
        for (int i = 0;i < size;i++){
            newElements[i] = elements[I];
        }
        elements = newElements;
        System.out.println(oldCapacity + "扩容为" + newCapacity);
    }
}

【第四部分】:链表类YYSingleLinkedList<E>

package com.example.linkedlist.list;

/**
 * 链表
 * @param <E>
 */
public class YYSingleLinkedList<E> extends AbstrctList<E> {

    /**
     * 首节点
     */
    private Node<E> first;

    /**
     * 内部类 --  节点
     * @param <E>
     */
    private static class Node<E>{
        /**
         * 数据元素
         */
        E element;

        /**
         * 下一个节点
         */
        Node<E> next;

        public Node(E element, Node<E> next) {
            this.element = element;
            this.next = next;
        }
    }

    @Override
    public void insertObjectAtIndex(int index, E element) {
        rangeCheckForInsert(index);
        if (index == 0){
            first = new Node<>(element,first);
        }else {
            Node<E> preNode = nodeOfIndex(index - 1);
            preNode.next = new Node<>(element,preNode.next);
        }
        size++;
    }

    @Override
    public E removeObjectAtIndex(int index) {
        rangeCheck(index);
        Node<E> node = first;
        if (index == 0){
            first = first.next;
        }else {
            Node<E> preNode = nodeOfIndex(index - 1);
            node = preNode.next;
            preNode.next = node.next;
        }
        size--;
        return node.element;
    }

    @Override
    public void clear() {
        size = 0;
        first = null;
    }

    @Override
    public E setObjectAtIndex(int index, E element) {
        Node<E> node = nodeOfIndex(index);
        E old = node.element;
        node.element = element;
        return old;
    }

    @Override
    public E objectAtIndex(int index) {
        return nodeOfIndex(index).element;
    }

    @Override
    public int indexOfObject(E element) {
        if (element == null){
            Node<E> node = first;
            for (int i = 0;i < size;i++){
                node = node.next;
                if (node.element == null) return I;
            }
        }else {
            Node<E> node = first;
            for (int i = 0;i < size;i++){
                node = node.next;
                if (node.element.equals(element)) return I;
            }
        }
        return ELEMENT_NOT_FOUND;
    }

    /**
     * 根据index索引 返回节点
     * @param index
     * @return
     */
    private Node<E> nodeOfIndex(int index){
        rangeCheck(index);
        //从首节点开始遍历
        Node node = first;
        for (int i= 0;i < index;i++){
            node = node.next;
        }
        return node;
    }

    @Override
    public String toString() {
        StringBuffer string = new StringBuffer();
        string.append("size=").append(size).append(", [");
        Node<E> node = first;
        for (int i = 0; i < size; i++) {
            if (i != 0){
                string.append(", ");
            }
            string.append(node.element);
            node = node.next;
        }
        string.append("]");
        return string.toString();
    }
  • private static class Node<E>是在YYLinkedList<E>链表类内部定义的,属于链表类的内部类,用关键字static进行修饰;
  • public void insertObjectAtIndex(int index, E element):往链表指定index位置插入元素element;
@Override
    public void insertObjectAtIndex(int index, E element) {
        if (index == 0){
            first = new Node<>(element,first);
        }else {
            Node<E> preNode = nodeOfIndex(index - 1);
            preNode.next = new Node<>(element,preNode.next);
        }
        size++;
    }
  • 以Snip20210330_73.png为例,在index = 2的位置插入一个新的节点(节点内部有新元素),其实现原理如下图所示:
Snip20210330_78.png
  • 首先根据当前index获取,前面一个节点preNode;
  • 然后创建一个新的节点(待插入的),让preNode的next改为指向新的节点;
  • 新的节点的next指向当前的节点即preNode.next;
  • 最后链表的长度size+1;
  • public E removeObjectAtIndex(int index):移除指定index位置的元素;
@Override
    public E removeObjectAtIndex(int index) {
        Node<E> node = first;
        if (index == 0){
            first = first.next;
        }else {
            Node<E> preNode = nodeOfIndex(index - 1);
            node = preNode.next;
            preNode.next = node.next;
        }
        size--;
        return node.element;
    }

移除index=2的位置的元素,其实现原理如下图所示:

Snip20210401_79.png
  • 首先根据当前index,获取前面一个节点preNode;
  • 然后将preNode的next指向当前节点的下一个节点,当前节点由于没有被外界指向(引用)会自动销毁;
  • 最后链表的长度size-1;
  • public void clear():清空链表,其实现原理如下图所示:
Snip20210401_81.png
  • 将链表的属性first置为null,那么链表的节点会由于没有外界的指向引用会依次回收;
  • 最后链表的长度size置为0;
  • public E setObjectAtIndex(int index, E element):设置指定index位置的元素;
@Override
    public E setObjectAtIndex(int index, E element) {
        Node<E> node = nodeOfIndex(index);
        E old = node.element;
        node.element = element;
        return old;
    }
  • 根据index获取到当前节点node,然后直接修改其element;
  • 后面几个方法的实现,逻辑简单就不做过多详述了;

链表练习案例:

【第一个案例:】删除链表中的节点
https://leetcode-cn.com/problems/shan-chu-lian-biao-de-jie-dian-lcof/

Snip20210401_82.png
  • 代码实现如下:
package com.example.linkedlist.链表;

public class _18_删除链表节点 {

    public void deleteListNode(ListNode node){
        node.value = node.next.value;
        node.next = node.next.next;
    }
}
  • 实现原理图如下:删除index=2的节点;
Snip20210401_84.png
  • index=2的节点不删除,将后面一个节点的值直接赋值给index=2的节点;且将index=2的节点的next指向后面一个节点(index= 3)的下一个节点;
  • index = 3的节点由于没有引用,会被回收;

【第二个案例:】反转一个链表
https://leetcode-cn.com/problems/fan-zhuan-lian-biao-lcof/

Snip20210401_85.png
  • 利用递归的方式实现,代码如下所示:
public class _24_反转链表 {

    /**
     * 返回的是反转之后的首节点
     * @param head
     * @return
     */
    public ListNode reveserseList(ListNode head){
        if (head == null || head.next == null) return head;

        ListNode newHead = reveserseList(head.next);
        head.next.next = head;
        head.next = null;
        return newHead;
    }
}

实现原理图:

Snip20210401_87.png
  • 利用迭代的方式实现代码如下:
public ListNode reveserseList2(ListNode head){
        ListNode newHead = null;
        while (head!=null){
            ListNode tempNode = head.next;
            head.next = newHead;
            newHead = head;
            head = tempNode;
        }
        return newHead;
    }
  • 实现原理图如下:
Snip20210401_89.png

【第三个案例:】环形链表
https://leetcode-cn.com/problems/linked-list-cycle/

Snip20210401_90.png
  • 代码实现如下:
public boolean hasCycle(ListNode head) {
        if (head == null || head.next == null) return false;

        ListNode slow = head;
        ListNode fast = head.next;
        while (fast != null && fast.next != null){
            if (slow == fast) return true;
            slow = slow.next;
            fast = fast.next.next;
        }
        return false;
    }
  • 实现原理如下图所示:
Snip20210401_91.png
  • 定义两个指针,一个慢指针slow初始指向首节点,一个快指针fast初始指向第二个节点;然后进入循环体,slow每次移动一个节点单位,fast每次移动两个节点单位,在循环过程中,如果存在slow=fast,那么表明此链表有环,否则没有环;
  • 快慢指针;

虚拟头节点

  • 有时候为了让代码更加精简,统一所有节点的处理逻辑,可以在最前面增加一个虚拟的头节点(不存储数据),结构如下图所示:
Snip20210401_93.png
  • YYLinkedList类代码改造如下:
public class YYLinkedList2<E> extends AbstrctList<E>{

    /**
     * 首节点
     */
    private Node<E> first;


    public YYLinkedList2() {
        first = new Node<>(null,null);
    }


    /**
     * 内部类 --  节点
     * @param <E>
     */
    private static class Node<E>{
        /**
         * 数据元素
         */
        E element;

        /**
         * 下一个节点
         */
        Node<E> next;

        public Node(E element, Node<E> next) {
            this.element = element;
            this.next = next;
        }
    }

    @Override
    public void insertObjectAtIndex(int index, E element) {
        rangeCheckForInsert(index);
        Node<E> preNode =  index == 0 ? first : nodeOfIndex(index - 1);
        preNode.next = new Node<>(element,preNode.next);

        size++;
    }

    @Override
    public E removeObjectAtIndex(int index) {
        rangeCheck(index);

        Node<E> preNode = index == 0 ? first : nodeOfIndex(index - 1);
        Node<E> node = preNode.next;
        preNode.next = node.next;

        size--;
        return node.element;
    }

    @Override
    public void clear() {
        size = 0;
        first = null;
    }

    @Override
    public E setObjectAtIndex(int index, E element) {
        Node<E> node = nodeOfIndex(index);
        E old = node.element;
        node.element = element;
        return old;
    }

    @Override
    public E objectAtIndex(int index) {
        return nodeOfIndex(index).element;
    }

    @Override
    public int indexOfObject(E element) {
        if (element == null){
            Node<E> node = first;
            for (int i = 0;i < size;i++){
                node = node.next;
                if (node.element == null) return I;
            }
        }else {
            Node<E> node = first;
            for (int i = 0;i < size;i++){
                node = node.next;
                if (node.element.equals(element)) return I;
            }
        }
        return ELEMENT_NOT_FOUND;
    }

    /**
     * 根据index索引 返回节点
     * @param index
     * @return
     */
    private Node<E> nodeOfIndex(int index){
        rangeCheck(index);
        //从首节点开始遍历
        Node node = first.next;
        for (int i= 0;i < index;i++){
            node = node.next;
        }
        return node;
    }

    @Override
    public String toString() {
        StringBuffer string = new StringBuffer();
        string.append("size=").append(size).append(", [");
        Node<E> node = first.next;
        for (int i = 0; i < size; i++) {
            if (i != 0){
                string.append(", ");
            }
            string.append(node.element);
            node = node.next;
        }
        string.append("]");
        return string.toString();
    }
}
  • 构造方法中新增虚拟头节点的初始化;
  • private Node<E> nodeOfIndex(int index):获取index位置的节点,应该从虚拟头节点的下一个节点开始;
  • public void insertObjectAtIndex(int index, E element):针对index = 0 做逻辑修改;
  • public E removeObjectAtIndex(int index):针对index = 0 做逻辑修改;
  • public String toString():遍历元素从虚拟头节点的下一个节点开始。
  • 针对链表对虚拟头节点的改造,不一定非要采用这种方案,保持原来的链表结构,一般来说没什么大的问题。

复杂度分析

  • public E objectAtIndex(int index):获取指定index位置的元素,内部会遍历链表找到目标元素;
  • 数据规模为size,即链表的元素个数;
  • 最坏情况复杂度:当index = size,一直遍历到链表尾部,其复杂度为:O(n)
  • 最好情况复杂度:当index = 0,其复杂度为:O(1)
  • 平均复杂度:其复杂度为:O(n)
  • public E setObjectAtIndex(int index, E element):设置指定index位置的元素,内部会遍历链表;
  • 最坏情况复杂度:当index = size,一直遍历到链表尾部,其复杂度为:O(n)
  • 最好情况复杂度:当index = 0,其复杂度为:O(1)
  • 平均复杂度:其复杂度为:O(n)
  • public void insertObjectAtIndex(int index, E element):往指定index位置插入元素,内部会遍历链表;
  • 最坏情况复杂度:当index = size,一直遍历到链表尾部,其复杂度为:O(n)
  • 最好情况复杂度:当index = 0,其复杂度为:O(1)
  • 平均复杂度:其复杂度为:O(n)
  • public E removeObjectAtIndex(int index):删除指定index位置的元素,内部会遍历链表;
  • 最坏情况复杂度:当index = size,一直遍历到链表尾部,其复杂度为:O(n)
  • 最好情况复杂度:当index = 0,其复杂度为:O(1)
  • 平均复杂度:其复杂度为:O(n)

单向循环链表

  • 单向链表尾节点的next指针指向首节点的链表属于单向循环链表;
  • 结构如下所示:
Snip20210402_115.png
  • 代码实现如下:
/**
 * 单向循环链表
 * @param <E>
 */
public class YYSingleCircleLinkedList<E> extends AbstrctList<E> {

    /**
     * 首节点
     */
    private Node<E> first;

    /**
     * 内部类 --  节点
     * @param <E>
     */
    private static class Node<E>{
        /**
         * 数据元素
         */
        E element;

        /**
         * 下一个节点
         */
        Node<E> next;

        public Node(E element, Node<E> next) {
            this.element = element;
            this.next = next;
        }

        @Override
        public String toString() {
            StringBuffer sb = new StringBuffer();
            sb.append(element).append("_").append(next.element);
            return sb.toString();
        }
    }

    @Override
    public void insertObjectAtIndex(int index, E element) {
        rangeCheckForInsert(index);
        if (index == 0){
            Node<E> newNode = new Node<>(element,first);
            //获取尾节点
            Node<E> last = size == 0 ? newNode : nodeOfIndex(size - 1);
            last.next = newNode;
            first = newNode;
        }else {
            Node<E> preNode = nodeOfIndex(index - 1);
            preNode.next = new Node<>(element,preNode.next);
        }
        size++;
    }

    @Override
    public E removeObjectAtIndex(int index) {
        rangeCheck(index);
        Node<E> node = first;
        if (index == 0){
            if (size == 1){
                first = null;
            }else {
                Node<E> last = nodeOfIndex(size-1);
                first = first.next;
                last.next = first;
            }
        }else {
            Node<E> preNode = nodeOfIndex(index - 1);
            node = preNode.next;
            preNode.next = node.next;
        }
        size--;
        return node.element;
    }

    @Override
    public void clear() {
        size = 0;
        first = null;
    }

    @Override
    public E setObjectAtIndex(int index, E element) {
        Node<E> node = nodeOfIndex(index);
        E old = node.element;
        node.element = element;
        return old;
    }

    @Override
    public E objectAtIndex(int index) {
        return nodeOfIndex(index).element;
    }

    @Override
    public int indexOfObject(E element) {if (element == null){
        Node<E> node = first;
        for (int i = 0;i < size;i++){
            node = node.next;
            if (node.element == null) return I;
        }
    }else {
        Node<E> node = first;
        for (int i = 0;i < size;i++){
            node = node.next;
            if (node.element.equals(element)) return I;
        }
    }
        return ELEMENT_NOT_FOUND;
    }

    /**
     * 根据index索引 返回节点
     * @param index
     * @return
     */
    private Node<E> nodeOfIndex(int index){
        rangeCheck(index);
        //从首节点开始遍历
        Node node = first;
        for (int i= 0;i < index;i++){
            node = node.next;
        }
        return node;
    }

    @Override
    public String toString() {
        StringBuffer string = new StringBuffer();
        string.append("size=").append(size).append(", [");
        Node<E> node = first;
        for (int i = 0; i < size; i++) {
            if (i != 0){
                string.append(", ");
            }
            string.append(node);
            node = node.next;
        }
        string.append("]");
        return string.toString();
    }
}
  • public void insertObjectAtIndex(int index, E element):往链表指定index位置插入元素element;
@Override
    public void insertObjectAtIndex(int index, E element) {
        rangeCheckForInsert(index);
        if (index == 0){
            Node<E> newNode = new Node<>(element,first);
            //获取尾节点
            Node<E> last = size == 0 ? newNode : nodeOfIndex(size - 1);
            last.next = newNode;
            first = newNode;
        }else {
            if (index == size - 1){
                Node<E> node = nodeOfIndex(index);
                Node<E> newNode = new Node<>(element,first);
                node.next = newNode;
            }else {
                Node<E> preNode = nodeOfIndex(index - 1);
                preNode.next = new Node<>(element,preNode.next);
            }
        }
        size++;
    }
  • 1> 往空链表中添加元素,原理如下图所示:
Snip20210402_121.png
  • 2> 往链表的首节点添加元素,原理如下图所示:
Snip20210402_120.png
  • 3> 往链表的尾节点添加元素,原理如下图所示:
Snip20210402_118.png
  • 4> 往链表中间节点index=2添加元素,原理如下图所示:
Snip20210402_119.png
  • public E removeObjectAtIndex(int index):移除指定index位置的元素
public E removeObjectAtIndex(int index) {
        rangeCheck(index);
        Node<E> node = first;
        if (index == 0){
            if (size == 1){
                first = null;
            }else {
                Node<E> last = nodeOfIndex(size-1);
                first = first.next;
                last.next = first;
            }
        }else {
            Node<E> preNode = nodeOfIndex(index - 1);
            node = preNode.next;
            if (index == size - 1){
                preNode.next = first;
            }else {
                preNode.next = node.next;
            }
        }
        size--;
        return node.element;
    }
  • 1> 当链表只有一个节点元素时删除节点,原理如下图所示:
Snip20210402_127.png
  • 2> 当删除链表的首节点,原理如下图所示:
Snip20210402_126.png
  • 3> 当删除链表的中间节点index=1,原理如下图所示:
Snip20210402_124.png
  • 4> 当删除链表的尾节点,原理如下图所示:
Snip20210402_125.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,542评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,822评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,912评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,449评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,500评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,370评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,193评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,074评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,505评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,722评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,841评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,569评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,168评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,783评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,918评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,962评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,781评论 2 354

推荐阅读更多精彩内容