zoukankan      html  css  js  c++  java
  • 链表

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

    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;
        }

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

  • 相关阅读:
    Fork/Join框架基本使用
    服务端高并发分布式架构演进之路
    Netty专题(一)-----计算机网络、TCP/ICP、linux网络I/O模型
    Nginx专题(四)-----https、nginx高可用
    Nginx专题(三)-----核心原理、跨域解决、防盗链、缓存以及压缩
    微信开放平台开发第三方授权登陆(四):微信小程序
    微信开放平台开发第三方授权登陆(三):微信公众号
    微信开放平台开发第三方授权登陆(二):PC网页端
    微信开放平台开发第三方授权登陆(一)-----开发前准备
    Mysql:如果数据存在则更新,不存在则插入
  • 原文地址:https://www.cnblogs.com/fflower/p/12392320.html
Copyright © 2011-2022 走看看