其他分享
首页 > 其他分享> > 数据结构-链表(1)

数据结构-链表(1)

作者:互联网

 

链表是一种用于存储数据集合的数据结构。链表有以下几个属性:

  

一、链表的分类

  链表大致可以分为这么几类:

  1. 单向链表
  2. 双向链表
  3. 存储较为高效的双向链表
  4. 循环链表
  5. 松散链表

二、LRU缓存淘汰算法

  在具体介绍链表之前,有必要先介绍一下关于LRU缓存技术:

  缓存是一种提高数据读取性能的技术,在硬件设计,软件开发中都有着非常广泛的应用,比如常见的CPU缓存、数据库缓存、浏览器缓存等等。

  缓存的大小是有限的,当缓存被用满的时,此时就需要对缓存做相应的清理,但是,具体哪些数据应该被清理出去?哪些数据应该被保留?这就需要缓存淘汰策略来决定。常见的策略有三种:

 那么接下来就有一个问题:如何使用链表来实现LRU缓存淘汰策略呢?

三、链表的底层存储结构

  相比于数组来说,链表是一种稍微复杂一点的数据结构,下面是链表和数组的内存分部对比图:

  

  从图中可以看出

  1. 对于数组来说,它需要一块连续的内存存储空间来存储,对内存的要求比较高,也就是说,如果我申请100M大小的数组的话,当内存中没有连续的、足够大的存储空间的时候,即便内存中剩余总可用空间大于100M,此时仍然会申请失败
  2. 对于链表来说,它恰恰相反,它并不需要一块连续的内存空间,它通过“指针”将一组零散的内存块串联起来使用,所以,如果我们申请的是100M大小的链表,当内存中剩余可用空间大于100M的时候,无论是否连续,申请都不会有问题。

四、链表的一些基本概念:

  1. 结点:链表是通过指针将自足零散的内存块串联在一起,所以,将每个内存块就是链表的一个"结点"
  2. 后继指针:每个链表的结点除了存储数据之外,还需要记录链路上下一个节点的地址,将记录下一个结点地址的指针叫做后继指针
  3. 头结点:整个链路中第一个结点称之为“头结点
  4. 尾节点:整个链路中最后一个结点称之为“尾节点
  5. 空地址NULL:当一个指针指向的不是下一个结点,而是空地址NULL,表示这是链路上的最后一个结点。

五、链表、数组的优缺点对比

  关于数组,其实在数组这一节,做了详细的讨论,这里不做多的赘述。

  链表与数组的优缺点对比:

  

  链表、数组与动态数组的时间复杂度对比:

    

 

单向链表

  链表通常是指单向链表,她包含了多个结点,每个结点有一个指向后继元素的指针,表中最后一个结点next指针为NULL,表示该链表结束。

申明一个链表

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 this.next;
    }
}

  链表的主要操作(时间复杂度均为O(1)):

  链表的辅助操作:

链表的遍历

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

  通过遍历链表来对链表元素进行计数:

/**
     * 统计链表节点的个数
     */
    public int ListLength(ListNode headNode) {
        int length = 0;
        ListNode currentNode = headNode;
        while (currentNode != null) {
            length++;
            currentNode = currentNode.getNext();
        }
        return length;
    }

  此时间复杂度为O(n),用于扫描长度为n的链表。

  空间复杂度为O(1),仅用于创建临时变量

单向链表的插入

  单向链表的插入可以分为以下3种情况

在单向链表的开头插入结点

  若需要在表头节点前插入一个新结点,只需要修改一个next指针,可通过如下两步完成:

   

    

在单向量表的结尾插入结点

  如果需要在表尾部插入新结点,则需要修改两个next指针

 

   

在单向链表的中间插入结点

  假设给定插入新结点的位置,在这种情况下,需要修改两个next指针:

     

      

代码实现:

/**
     * 单向链表List节点进行插入操作
     */
    public ListNode InsertInLinkedList(ListNode headNode, ListNode nodeToInsert, int position) {
        //如果链表为空,则插入的节点即为头结点
        if (headNode == null) {
            return nodeToInsert;
        }
        //获取该链表的节点数
        int size = ListLength(headNode);
        if (position < 1 || position > size + 1) {
            System.out.println("Position of node to insert is invalid.The valid input are 1 to "
                    + (size + 1));
            return headNode;
        }
        //否则,插入元素要么是在头插入,要么是在尾节点,或是中间
        if (position == 1) {
            nodeToInsert.setNext(headNode);
            return nodeToInsert;
        } else {
            //在链表的中间或尾部插入
            ListNode previousNode = headNode;
            int count = 1;
            while (count < position - 1) {
                previousNode = previousNode.getNext();
                count++;
            }
            ListNode currentNode = previousNode.getNext();
            nodeToInsert.setNext(currentNode);
            previousNode.setNext(nodeToInsert);
        }

        return headNode;
    }

  时间复杂度为O(n)。在最坏情况下,可能需要在链表尾部插入结点。

  空间复杂度为O(1),仅用于创建一个临时变量。

单向链表的删除

  单向链表的删除操作,也分为三种情况:

删除单向链表表头结点

  删除链表的第一个结点,可以通过两步实现:

    

 

     

删除单向链表的最后一个结点

  这种情况下,操作比删除第一个结点要麻烦一点,因为算法需要找到表尾节点的前驱节点。这需要三步来实现:

     

       

       

删除单向链表中间一个结点

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

      

 

       

代码实现:

/**
     * 单向链表List的删除操作
     */
    public ListNode deleteNodeFromeLinkedList(ListNode headNode, int position) {
        int size = ListLength(headNode);
        if (position > size || position < 1) {
            System.out.println("Postition of node to delete is invalid.The valid inputs are 1 to "
                    + size);
            return headNode;
        }
        if (position == 1) {
            ListNode currentNode = headNode.getNext();
            headNode = null;
            return currentNode;
        } else {
            ListNode preivousNode = headNode;
            int count = 1;
            while (count < position) {
                preivousNode = preivousNode.getNext();
                count++;
            }
            ListNode currentNode = preivousNode.getNext();
            preivousNode.setNext(currentNode.getNext());
            currentNode = null;
        }
        return headNode;
    }

  时间复杂度为O(n)。在最差情况下,可能需要删除链表的表尾节点。

  空间复杂度为O(1),仅用于创建一个临时变量

删除单向链表

  该操作通过将当前结点存储在临时变量中,然后释放当前结点(空间)的方式来完成。当时放完当前结点(空间)后,移动到下一个结点并将其存储在临时变量中,然后不断重复该过程直至释放所有结点。

代码实现:

/**
     * 删除单向链表
     */
    public void deleteLinkedList(ListNode headNode) {
        ListNode auxilaryNode,iterator = headNode;
        while (iterator != null){
            auxilaryNode = iterator.getNext();
            iterator = null;
            iterator = auxilaryNode;
        }
    }

  时间复杂度为O(n),用扫描大小为n的整个建链表

  空间复杂度为O(1),用于创建临时变量

标签:结点,ListNode,next,链表,headNode,数据结构,指针
来源: https://www.cnblogs.com/Gxiaobai/p/11331165.html