链表

一.常见的缓存淘汰策略:

1.先进先出策略FIFO

2.最少使用策略LFU

3.最近最少使用策略LRU

二.链表

(一)链表的定义:链表是物理存储单元上非连续的、非顺序的存储结构,它由一个个结点,通过指针联系起来的,每个结点包括数据和指针。

(二)链表结构:

1.单链表

通过“指针”将一组零散的内存块串联起来使用。内存块称为结点;记录下个结点地址的指针称为后继指针next;第一个结点为头结点,用来记录链表的基地址;最后一个结点为尾结点,指针指向NULL。

链表的插入、删除操作,只需考虑相邻结点的指针变化,不需要数据搬移,时间复杂度为O(1)随机访问的时间复杂度为O(n)

2.循环链表

是一种特殊的单链表。尾结点的指针指向链表的头结点。

从链尾到链头比较方便。当要处理的数据具有环形结构特点时,适合采用循环链表。例如约瑟夫问题。

3.双向链表

每个结点有两个指针,分别为后继指针next和前驱指针prev。找到前驱结点的时间复杂度为O(1)。

4.双向循环链表

三.

1.删除操作

(1)删除结点中“值等于某个给定值”的结点

从头结点开始一个一个遍历,直到找到值等于给定值的结点,再删除。删除操作时间复杂度为O(1),查找操作时间复杂度为O(n);总时间复杂度为O(n)。

(2)删除给定指针指向的结点

对于单链表来说,要从头结点开始遍历找到给定结点的前驱结点,再删除。时间复杂度为O(n)。

对于双向链表来说,可以直接找到前驱结点。时间复杂度为O(1)。

2.插入操作

在某个结点前插入一个结点,单链表要从头遍历,找到给定结点再在其前面插入,时间复杂度为O(n);双向链表的时间复杂度为O(1)。

3.有序链表的按值查询

可以记录上次查找的位置q,每次查询时根据查找的值与q相比较就知道了接下来是向前查还是向后查,平均只需要查找一半的数据。

四.用空间换时间/用时间换空间

当内存空间充足时,如果更追求代码执行速度,可以选择空间复杂度较高,时间复杂度较低的数据结构。相反则用时间换空间。

五.数组与链表比较

1.数组在实现上使用连续的内存空间,可以借助CPU缓存机制,预读数组中的数据,效率更高。链表在内存中不连续存储,不能使用CPU缓存机制。

CPU缓存机制:CPU运行速度非常快,每次去内存中取数据很耗时。所以CPU设置了缓存。提前把用到的数据存储在缓存里,下次再用时从缓存中取能减少耗时。什么样的情况下数据会提前加载到缓存呢?即当某个元素被用到的时候,那么这个元素地址附近的元素也会被加载到缓存。例如数组1,2,3,4,当1被用到时,CPU认为既然1被用到了,那么2,3,4也很大概率可能被用到,所以都被加载到缓存里,当用到2,3,4的时候,直接从缓存里取。

2.数组大小固定,链表支持动态扩容。

六.基于链表实现LRU缓存淘汰算法

用一个有序链表,越靠近尾部的结点是越早之前访问的。当有一个新的数据被访问时,从头结点开始顺序遍历链表

(1)如果此数据已经存在链表中了,遍历此数据对应的结点,删除,再插入到链表头。

(2)如果链表中无此数据,分两种情况

  <1>此时缓存未满,将此数据插入到链表头

  <2>此时缓存已满,删除链表尾结点,将此数据插入到链表头。

时间复杂度为O(n)

七.代码

class Node{
    /**
     * 结点的值
     */
    int data;
    /**
     * 结点引用,指向下一个结点
     */
    Node next = null;
    public Node(int data){
        this.data = data;
    }

   /**
     * 哨兵结点
     */
    Node head = new Node(0);
    /**
     * 链表长度
     */
    int length = 0;

    /**
     * 1.从尾结点开始添加结点
     * @param val
     */
    public void tailInsert(int val){
        Node temp = head;
        //遍历链表,直到到达尾结点
        while (temp.next!=null){
            temp = temp.next;
        }
        //新建结点
        temp.next = new Node(val);
    }

    /**
     * 2.从头结点插入
     * @param val
     */
    public void headInsert(int val){
        Node temp = head;
        //新结点
        Node newNode = new Node(val);
        //新节点的指针指向头结点的下一个结点
        newNode.next = temp.next;
        //头结点指针指向新结点
        temp.next = newNode;
    }

参考:王争《数据结构与算法之美》

原文地址:https://www.cnblogs.com/fflower/p/12392320.html