数据结构——链表

目录

1、属性

2、链表和数组的区别

2.1、数组概述

2.2、数组和链表优缺点

2.3、链表和数组的比较

3、单向链表

3.1、单向链表的基本操作

3.1.1、链表的遍历
3.1.2、链表的插入
3.1.2.1、在单向链表的开头插入结点
3.1.2.2、在单向链表的结尾插入结点
3.1.2.3、在单向链表的中间插入结点
3.1.2.4、函数实现
3.1.3、链表的删除
3.1.3.1、删除链表的表头
3.1.3.2、删除链表的表尾
3.1.3.3、删除链表中间的结点
3.1.3.4、函数实现
3.1.4、链表的释放

4、双向链表

4.1、双向链表的插入

4.1.1、在双向链表的开头插入结点
4.1.2、在双向链表的末尾插入结点
4.1.3、在双向链表的中间插入一个结点
4.1.4、函数实现

4.2、双向链表的删除

4.2.1、删除双向链表的第一个结点
4.2.2、删除双向链表的最后一个结点
4.2.3、删除双向链表中间的一个结点
4.2.4、代码实现

5、循环链表

5.1、遍历循环链表

5.2、在循环链表的表尾插入结点

5.3、在循环链表的表头插入结点

5.4、删除循环链表中的最后一个结点

5.5、删除循环链表中的第一个结点

6、松散链表

6.1、在松散链表查找一个元素

6.2、在松散链表中插入一个元素

6.3、在松散链表中执行移动操作

正文

1、属性

①:相邻元素之间通过指针连接。
②:最后一个元素的后继指针为NULL。
③:链表的空间能够按需分配。
④:没有内存空间的浪费。


图1-1 链表示意图

2、链表和数组的区别

链表和数组都可以用于存储数据集合,由于两者的用途相同,所以需要对它们的用法进行区分。

2.1、数组概述

整个数组所有元素存储在操作系统分配的一个内存块中,通过使用特定元素的索引作为数组下标,可以在常数时间内访问数组元素。


图2-1 数组示意图

2.2、数组和链表优缺点

  • 数组的优点:
    1、简单且易用 。
    2、访问元素为常数时间。
  • 数组的缺点:
    1、大小固定,在使用数组前需要指定数组大小。
    2、需要分配一个连续空间块,当数组规模过大时,就无法分配存储整个数组的内存空间。
    3、如果要在数组中插入元素,可能需要移动存储在数组中的其他元素,这样才能腾出指定的位置来放插入的元素。
  • 链表的优点:
    1、链表可以在常数时间内扩展。
    2、容易添加新元素。
  • 链表的缺点:
    1、访问单个元素的时间开销问题过大。
    2、有时很难对链表操作,如果要删除最后一项,倒数第二项必须更改后继指针为NULL。这需要从头遍历链表,找到倒数第二个结点的链接,并设置其后置指针为NULL。
    3、链表中的额外指针引用需要浪费内存。

2.3、链表和数组的比较

参数 链表 数组
索引 O(n) O(1)
在最前端插入/删除 O(1) O(n),如果数组空间没有填满(需要移动元素)
在最末端插入 O(n) O(1),如果数组空间没有填满
在最末端删除 O(n) O(1)
在中间插入 O(n) O(n),如果数组空间没有填满(需要移动元素)
在中间删除 O(n) O(n) ,如果数组空间没有填满(需要移动元素)
空间浪费 O(n) 0

3、单向链表

链表通常是指单向链表,它包含多个结点,每个结点都有一个指向后继元素的next(下一个)指针。表中最后一个结点的next值为NULL,表示链表的结束。


图3-1 单向链表

下面是单向链表的类型声明:

public class ListNode {
    private int data;
    private ListNode next;

    public ListNode(int data){
        this.data=data;
    }

    public void setData(int data){
        this.data=data;
    }

    public int getData(){
        return data;
    }

    public void setNext(ListNode next){
        this.next=next;
    }

    public ListNode getNext() {
        return next;
    }
}

3.1、单向链表的基本操作

  • 遍历链表。
  • 在链表中插入一个元素。
  • 从链表中删除一个元素。
  • 删除链表。
3.1.1、链表的遍历

假设表头结点的指针指向链表中的第一个结点。遍历链表需要完成以下步骤:

  • 沿指针遍历。
  • 遍历时显示结点的内容。
  • 当next指针为NULL时结束遍历。

下面是遍历函数的声明:

    public int ListLength(ListNode headNode){
        int length=0;
        ListNode currentNode=headNode;
        while (currentNode!=null){
            length++;
            System.out.print(currentNode.getData());
            currentNode=currentNode.getNext();
        }
        return length;
    }
3.1.2、链表的插入
3.1.2.1、在单向链表的开头插入结点

若要在当前表头结点前插入一个新结点,只需要修改一个next指针(新结点的next指针),如下步骤:

  • 更新新结点的next指针,使其指向当前的表头结点。


    图3-2 更新新结点
  • 更新表头指针的值,使其指向新结点。


    图3-3 更新表头指针
3.1.2.2、在单向链表的结尾插入结点

若要在表尾后插入一个新结点,则需要修改两个next指针(最后一个结点的next指针和新结点的next指针),如下步骤:

  • 新结点的next指针指向NULL。


    图3-4 更新新结点
  • 最后一个结点的next指针指向新结点。


    图3-5 更新表尾结点
3.1.2.3、在单向链表的中间插入结点

假设在这种情况下插入新结点,则需要修改两个next指针,如下步骤:

  • 如果要在位置3增加一个元素,则首先要将指针定位在链表的位置2。即需要从表头开始经过两个结点,然后插入新结点。为了简单起见,假设第二个结点为位置结点,新结点的next指针指向位置结点的下一个结点。


    图3-6 更新新结点
  • 位置结点的next指针指向新结点。


    图3-7 更新位置结点
3.1.2.4、函数实现
    ListNode InsertInLinkedList(ListNode headNode,ListNode nodeToInsert,int position){
        if(headNode==null){
            return headNode;
        }
        //求链表长度
        int size=ListLength(headNode);
        if(position>size+1||position<1){
            System.out.print("position 的范围在1-(size+1)");
            return headNode;
        }
        if(position==1){
            //在链表开头插入
            nodeToInsert.setNext(headNode);
            return nodeToInsert;
        }
        else {
            //在链表中间或者末尾插入
            //位置结点
            ListNode previousNode=headNode;
            int count=1;
            while (count<position-1){
                previousNode=headNode.getNext();
                count++;
            }
            //位置结点的下一结点
            ListNode currentNode=previousNode.getNext();
            //更新新结点
            nodeToInsert.setNext(currentNode);
            //更新位置结点
            previousNode.setNext(nodeToInsert);
        }
        return headNode;
    }
3.1.3、链表的删除
3.1.3.1、删除链表的表头
  • 创建一个临时结点,它指向表头指针所指向的结点。


    图3-8 创建临时结点
  • 修改表头指针的值,使其指向下一个结点,并移除临时结点。


    图3-9 修改表头并移除临时结点
3.1.3.2、删除链表的表尾

该操作比删除链表的第一个结点要稍微复杂一些,因为算法需要找到表尾结点的前驱结点。分三步实现:

  • 遍历链表,在遍历时还要保存前驱结点的地址,当遍历到链表的表尾时,将有两个指针,分别是表尾结点的指针和指向表尾结点的前驱结点的指针。


    图3-10 表尾及前驱结点
  • 将表尾的前驱结点的next指针更新为NULL。


    图3-11 更新前驱结点
  • 移除表尾结点


    图3-12 移除表尾
3.1.3.3、删除链表中间的结点

这种情况下,被删除的结点总是位于两个结点之间,因此不需要更新表头和表尾的指针。分两步实现:

  • 遍历链表,在遍历时保存前驱结点的地址,一旦找到被删除的结点,将前驱结点的next指针的值更新为被删除的结点的next指针的值。


    图3-13 更新前驱结点
  • 移除需删除的当前结点


    图3-14 移除结点
3.1.3.4、函数实现
ListNode DeleteNodeFromLinkedList(ListNode headNode,int position){
        //求链表长度
        int size=ListLength(headNode);
        if(position>size||position<1){
            System.out.print("position 的范围在1-size");
            return headNode;
        }
        if(position==1){
            //删除表头
            ListNode currentNode=headNode.getNext();
            headNode=null;
            return currentNode;
        }else {
            //删除中间或表尾
            //前驱结点
            ListNode previousNode=headNode;
            int count=1;
            while(count<position-1){
                previousNode=previousNode.getNext();
                count++;
            }
            //要删除的结点
            ListNode currentNode=previousNode.getNext();
            //更新前驱结点
            previousNode.setNext(currentNode.getNext());
            //删除当前结点
            currentNode=null;
        }
        return headNode;
    }
3.1.4、链表的释放

这个操作通过将当前结点存储在临时变量中,然后释放当前结点。当释放完当前结点后,移动到下一个结点并将其存储在临时变量中,然后不断重复该过程直至释放所有结点。代码如下:

    void DeleteLinkedList(ListNode headNode){
        //临时变量 和 迭代器
        ListNode tempNode,iterator=headNode;
        while (iterator!=null){
            tempNode=iterator.getNext();
            iterator=null;
            iterator=tempNode;
        }
    }

4、双向链表

  • 双向链表的优点:对于链表中一个给定的结点,可以从两个方向进行操作。
  • 双向链表的缺点:每个结点需要一个额外的指针,因此需要更多的开销,插入和删除时更费时。

下面给出双向链表的类型声明:

public class DLLNode {
    private int data;
    private  DLLNode previous;
    private  DLLNode next;

    public DLLNode(int data){
        this.data=data;
    }

    public void setData(int data) {
        this.data = data;
    }

    public int getData() {
        return data;
    }

    public void setPrevious(DLLNode previous) {
        this.previous = previous;
    }

    public DLLNode getPrevious() {
        return previous;
    }

    public void setNext(DLLNode next) {
        this.next = next;
    }

    public DLLNode getNext() {
        return next;
    }
}

4.1、双向链表的插入

分为以下三种情况:

  • 在链表的开头前插入一个新结点。
  • 在链表的末尾后插入一个新结点。
  • 在链表的中间插入一个新结点。
4.1.1、在双向链表的开头插入结点
  • 将新结点的后继指针更新为指向当前的表头结点,将前驱指针赋值为NULL。


    图4-1 更新新结点
  • 将表头结点的前驱指针更新为指向新结点,然后将新结点作为表头。


    图4-2 更新表头
4.1.2、在双向链表的末尾插入结点

这种情况下需要遍历到链表的最后,然后插入新结点。

  • 将新结点的后继指针赋值为NULL,前驱指针指向表尾结点。


    图4-3 更新新结点
  • 将表尾结点的后继指针更新为指向新结点。


    图4-4 更新表尾结点
4.1.3、在双向链表的中间插入一个结点
  • 将新结点的后继指针赋值为位置结点的后继结点,前驱指针指向位置结点。


    图4-5 更新新结点
  • 将位置结点的后继结点的前驱指针赋值为新结点,将位置结点的后继指针赋值为新结点。


    图4-6 更新位置结点、后继结点
4.1.4、函数实现
public DLLNode DLLInsert(DLLNode headNode,DLLNode nodeToInsert,int position){
        if(headNode==null){
            return nodeToInsert;
        }
        //求链表长度
        int size=ListLength(headNode);
        if(position<1||position>size+1){
            System.out.print("position 的范围在1-(size+1)");
            return headNode;
        }
        if(position==1){
            //在链表开头插入
            nodeToInsert.setNext(headNode);
            headNode.setPrevious(nodeToInsert);
            return nodeToInsert;
        }else {
            //在链表中间或者表尾插入
            //位置结点
            DLLNode previousNode=headNode;
            int count=1;
            while (count<position-1){
                previousNode=previousNode.getNext();
                count++;
            }
            //位置结点的后继结点
            DLLNode currentNode=previousNode.getNext();
            //更新新结点
            nodeToInsert.setNext(currentNode);
            nodeToInsert.setPrevious(previousNode);
            //更新位置结点
            previousNode.setNext(nodeToInsert);
            //更新位置结点的后继结点
            if(currentNode!=null){
                currentNode.setPrevious(nodeToInsert);
            }
        }
        return headNode;
    }

4.2、双向链表的删除

分为以下三种情况:

  • 删除链表的表头。
  • 删除链表的表尾。
  • 删除链表中间的一个结点。
4.2.1、删除双向链表的第一个结点
  • 创建一个临时结点,它与表头指向同一个结点。


    图4-7 临时结点
  • 修改表头指针,使其指向下一个结点,将表头指针的前驱指针赋值为NULL,然后移除临时结点。


    图4-8 更新表头、移除临时结点
4.2.2、删除双向链表的最后一个结点

需要找到表尾结点的前驱结点,分以下三步进行:

  • 遍历链表,同时保存前驱结点的地址。当遍历到表尾时,有两个指针分别是指向表尾结点的指针和指向表尾结点的前驱结点的指针。


    图4-9 表尾结点、表尾结点的前驱结点
  • 更新表尾结点的前驱结点的后继指针的值为NULL。


    图4-10 更新表尾结点的前驱结点
  • 移除表尾结点。


    图4-11 移除表尾结点
4.2.3、删除双向链表中间的一个结点
  • 与一种删除情况类似,在遍历链表时保存前驱结点。一旦找到要删除的结点,更改前驱结点的后继指针使其指向被删除结点的后继结点,更改被删除结点的后继结点的前驱指针指向被删除结点的前驱结点。


    图4-12 更新删除结点的前驱结点和后继结点
  • 移除被删除的当前结点。


    图4-13 删除结点
4.2.4、代码实现
public DLLNode DLLDelete(DLLNode headNode,int position){
        int size=ListLength(headNode);
        if(position<1||position>size){
            System.out.print("position 的范围在1-size");
            return headNode;
        }
        if(position==1){
            //删除链表的第一个结点
            DLLNode currentNode=headNode.getNext();
            currentNode.setPrevious(null);
            headNode=null;
            return currentNode;
        }else {
            //删除链表的中间结点或者最后一个结点
            //前驱结点
            DLLNode previousNode=headNode;
            int count=1;
            while(count<position-1){
                previousNode=previousNode.getNext();
                count++;
            }
            //删除结点
            DLLNode currentNode=previousNode.getNext();
            //后继结点
            DLLNode laterNode=currentNode.getNext();
            //更新前驱结点
            previousNode.setNext(laterNode);
            //更新后继结点
            if(laterNode!=null){
                laterNode.setPrevious(previousNode);
            }
            currentNode=null;
        }
        return headNode;
    }

5、循环链表

在单向链表和双向链表中,都采用NULL值作为链表的结束。然而循环链表没有结束标志。下面给出循环链表的类型声明:

public class CLLNode {
    private int data;
    private CLLNode next;
    public CLLNode(int data){
        this.data=data;
    }

    public void setData(int data) {
        this.data = data;
    }

    public int getData() {
        return data;
    }

    public void setNext(CLLNode next) {
        this.next = next;
    }

    public CLLNode getNext() {
        return next;
    }
}

5.1、遍历循环链表

循环链表可以通过标记为表头的结点进行访问,从标记为表头的结点开始遍历,利用虚拟结点——当前结点,当当前结点再次达到开始结点时结束遍历。代码实现如下:

 public int CircularListLength(CLLNode headNode){
        int length=0;
        CLLNode currentNode=headNode;
        while (currentNode!=null){
            length++;
            currentNode=currentNode.getNext();
            if(currentNode==headNode){
                break;
            }
        }
        return length;
    }

5.2、在循环链表的表尾插入结点

在由表头开始的循环链表的表尾后插入一个新结点,也就是在表尾结点和第一个结点之间插入该新结点。

  • 创建一个新结点,并且初始化其next指针指向该结点自身。


    图5-1 创建新结点
  • 更新新结点的next指针为表头结点,然后循环遍历链表直至表尾。即插入位置应为循环链表中下一个结点是表头结点的结点位置。


    图5-2 更新新结点
  • 更新表头结点的前驱结点的next指针指向新结点。


    图5-3 更新表头的前驱结点
  • 代码实现:
 public void InsertAtEndInCLL(CLLNode headNode,CLLNode nodeToInsert){
        CLLNode currentNode=headNode;
        while (currentNode.getNext()!=headNode){
            currentNode=currentNode.getNext();
        }
        //初始化
        nodeToInsert.setNext(nodeToInsert);
        if(headNode==null){
            headNode=nodeToInsert;
        }
        else {
            //更新新结点
            nodeToInsert.setNext(headNode);
            //更新表头的前驱结点
            currentNode.setNext(nodeToInsert);
        }
    }

5.3、在循环链表的表头插入结点

与上述表尾后插入结点的唯一区别是,在插入新结点后,还需要更新指针。

  • 创建一个新结点,并且初始化其next指针指向该结点自身。


    图5-4 创建新结点
  • 更新新结点的next指针为表头结点,然后循环遍历链表直至表尾。即插入位置应为循环链表中下一个结点是表头结点的结点位置。


    图5-5 更新新结点
  • 更新表头结点的前驱结点的next指针指向新结点。


    图5-6 更新表头的前驱结点
  • 设置新结点为表头结点。


    图5-7 设置新结点为表头
  • 代码实现:

 public void InsertAtBeginInCLL(CLLNode headNode,CLLNode nodeToInsert){
        CLLNode currentNode=headNode;
        while (currentNode.getNext()!=headNode){
            currentNode=currentNode.getNext();
        }
        //初始化
        nodeToInsert.setNext(nodeToInsert);
        if(headNode==null){
            headNode=nodeToInsert;
        }
        else {
            //更新新结点
            nodeToInsert.setNext(headNode);
            //更新表头的前驱结点
            currentNode.setNext(nodeToInsert);
            //设置新结点为表头
            headNode=nodeToInsert;
        }
    }

5.4、删除循环链表中的最后一个结点

为了删除最后一个结点,需要遍历循环链表找到倒数第二个结点。该结点成为了新的表尾结点,其next指针指向第一个结点。

  • 遍历循环链表,找到表尾结点及其前驱结点。


    图5-8 遍历循环链表
  • 更新表尾结点的前驱结点的next指针,使其指向表头结点。


    图5-9 更新表尾结点的前驱结点
  • 移除表尾结点。


    图5-10 移除表尾结点
  • 代码实现:

public void DeleteLastNodeFromCLL(CLLNode headNode){
        //表尾的前驱结点
        CLLNode temp=headNode;
        //表尾
        CLLNode currentNode=headNode;
        if(headNode==null){
            System.out.print("List is empty");
            return;
        }
        while (currentNode.getNext()!=headNode){
            temp=currentNode;
            currentNode=currentNode.getNext();
        }
        //更新表尾的前驱结点
        temp.setNext(headNode);
        //移除表尾
        currentNode=null;
        return;
    }

5.5、删除循环链表中的第一个结点

只需要将表尾结点的next指针指向第一个结点的后继结点。

  • 遍历循环链表找到表尾结点,就是要删除结点的前驱结点。


    图5-11 遍历循环链表
  • 创建一个指向表头结点的临时结点,更新表尾结点的next指针,使其指向第一个结点的后继结点。


    图5-12 更新表尾结点
  • 修改表头指针的值,使其指向其后继结点,移除临时结点。


    图5-13 更新表头并移除临时结点
  • 代码实现:

public void DeleteFrontNodeFromCLL(CLLNode headNode){
        //临时结点
        CLLNode temp=headNode;
        //表尾结点
        CLLNode currentNode=headNode;
        if(headNode==null){
            System.out.print("List Empty");
            return;
        }
        while (currentNode.getNext()!=headNode){
            currentNode=currentNode.getNext();
        }
        //更新表尾结点
        currentNode.setNext(headNode.getNext());
        //更新表头
        headNode=headNode.getNext();
        //移除临时结点
        temp=null;
        return;
    }

6、松散链表

与数组相比,链表的最大优势在于,在任何位置插入元素的时间开销仅为O(1)。然而在链表中查找某元素的时间开销则是O(n)。下面介绍单向链表的简单变种,称为松散链表。
松散链表中的每个结点存储多个元素(简称为块)。而每一块中的所有结点由循环链表链接在一起。


图6-1 松散链表
  • 假设在任何时候松散链表的元素个数不超过n,为了进一步简化问题,假设除了最后一块外,其它块恰好有⌈ √n⌉(表示不小于根号n的最小整数),所以在任何时候,松散链表中的个数不会超过⌊√n⌋(表示不大于根号n的最大整数)。举个例子,n为10的时候,⌈ √n⌉=4,⌊√n⌋=2。

6.1、在松散链表查找一个元素

在松散链表中查找第k个元素,时间复杂度为O(√n)。具体描述如下:

  • 遍历链表找到包含第k个结点的块,已知每块⌈ √n⌉个结点,所以在第⌈ k/⌈ √n⌉⌉块。举个例子,n为10的时候,⌈ √n⌉=4,假设此时k=9。那么⌈ 9/4⌉=3,即不小于9/4的最小整数。因为k<=n,所以最多遍历√n个块,那么时间复杂度也就是O(√n)。
  • 接着在这个块的循环链表找到第(k mod ⌈ √n⌉)个结点。这个过程的时间复杂度同样也是O(√n),原因是每块中的结点个数最多为⌈ √n⌉。

6.2、在松散链表中插入一个元素

当插入结点的时候,可能需要调整松散链表中的结点以保证前面提到的链表属性,也就是除了最后一块外,其它块恰好有⌈ √n⌉个结点。举个例子,如下图:


图6-2 插入元素22

6.3、在松散链表中执行移动操作

上述插入过程中会对一些元素进行移动,使其满足松散链表的相关性质。其实每次移动操作包括从块中的循环链表的表尾移除一个结点,并在下一块中的循环链表的表头插入一个结点,时间复杂度为O(1)。那么可以推出在松散链表中插入一个元素的时间复杂度为O(√n),因为最多执行√n次移动操作。

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

推荐阅读更多精彩内容

  • 一些概念 数据结构就是研究数据的逻辑结构和物理结构以及它们之间相互关系,并对这种结构定义相应的运算,而且确保经过这...
    Winterfell_Z阅读 5,529评论 0 13
  • 本文内容:1、 什么是链表?2、 链表共分几类?3、 链表的 C 实现! 总表:《数据结构?》 工程代码 Gith...
    半纸渊阅读 39,527评论 0 54
  • 代码GitHub地址 链表概述 数组和链表都是线性存储结构的基础实现,栈和队列都是线性存储结构的应用 数组优缺点 ...
    HikariCP阅读 1,290评论 0 0
  • //联系人:石虎QQ: 1224614774昵称:嗡嘛呢叭咪哄 一、链表的历史 链表开发于1955-56,由当时所...
    石虎132阅读 842评论 0 8
  • 本文内容取自于小甲鱼的数据结构与算法。http://www.jianshu.com/p/230e6fde9c75 ...
    阿阿阿阿毛阅读 2,757评论 0 7