如何实现LFU缓存(最近最少频率使用)

news2024/12/23 16:36:36

目录

1.什么是LFU缓存?

2.LFU的使用场景有哪些?

3.LFU缓存的实现方式有哪些?

4.put/get 函数实现具体功能


1.什么是LFU缓存?

        LFU缓存是一个具有指定大小的缓存,随着添加元素的增加,达到容量的上限,会最先移除最少使用频率的值。如果最少使用频率的值有多个,按照插入的先后顺序移除要求put/get操作的时间复杂度O(1)

2.LFU的使用场景有哪些?

       redis 缓存的过期淘汰策略,leetcode 460. LFU 缓存设计

3.LFU缓存的实现方式有哪些?

      LFU实现的底层数据结构图如下

     有两种实现方式,核心都是两个hashmash

     两个hashmap的含义是

      cache是 map(key,node), key是正常的key值,value是node节点。

      freqMap是map(key,nodelist), key是频率值,value是相同频率的key组成的双向链表。插入使用头插法,尾结点是最早未使用的节点,删除节点时删除尾结点。

      实现思路:

      要求get/put操作时间复杂度是O(1),cache(map)保证get/put操作的时间复杂度是O(1),freqMap中双向链表保证在头部和尾部添加元素的时间复杂度是O(1),自实现的双向链表保证删除任何元素的时间复杂度是O(1)

4.put/get 函数实现具体功能

put(key,value)函数功能实现

cache Map(key,node) 其中key是添加元素的key
freqMap map(key,nodelist)其中 key是频率
分三种情况
1.key不在在cache中不存在,没有达到容量上限
    - freq++
    - nodelist新增node,freqMap新增(key,nodelist),cacheMap 新增(key,node),size++
2.key在cache中不存在,达到容量上限
    - 获得最小频率的nodelist中的第一个元素,freqMap中删除的listnode的node,cache中也删除对应的key
    - freq++
    - nodelist新增node,freqMap新增(key,nodelist)和cacheMap新增(key,node)
    - size不变(因为删除了一个元素,添加了一个元素)
3.key在cache中
步骤如下:
     -  更新value值
     -  freqMap中删除的listnode的node,如果当前频率是最小值且list列表为空,min++
     -  freq++
     -  nodelist新增node,freqMap新增(key,nodelist),cache新增(key,node)

get(key)函数功能实现

分两种情况
1.key不在缓存中,直接返回-1.
2.key在缓存中
    - freqMap 删除旧key中nodelist对应的node节点(如果node的频率等于最小频率,且删除后的nodelist为空,更新最小频率的标记min为min+1),
    - freq++
    - freqMap中新key对应的nodelist新增node节点,更新cache中的(key,node)信息

两个hashmap,其中nodelist使用jdk自带的linkedhashset的代码实现如下

package com.mashibing.my;

import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;

public class LFUCache3 {
    class Node {
        int key;
        int value;
        int freq;

        public Node(int key, int value) {
            this.key = key;
            this.value = value;
        }
    }

    Map<Integer, Node> cache = new HashMap<>();
    Map<Integer, LinkedHashSet<Node>> freqMap = new HashMap<>();
    int size;
    int capacity;
    int min;

    public static void main(String[] args) {
        LFUCache3 cache = new LFUCache3(2);
        cache.put(1, 1);
        cache.put(2, 2);
        // 返回 1
        System.out.println(cache.get(1));
        cache.put(3, 3);    // 去除 key 2
        // 返回 -1 (未找到key 2)
        System.out.println(cache.get(2));
        // 返回 3
        System.out.println(cache.get(3));
        cache.put(4, 4);    // 去除 key 1
        // 返回 -1 (未找到 key 1)
        System.out.println(cache.get(1));
        // 返回 3
        System.out.println(cache.get(3));
        // 返回 4
        System.out.println(cache.get(4));
    }


    public LFUCache3(int capacity) {
        this.capacity = capacity;
    }

    public int get(int key) {
        Node node = cache.get(key);
        if (node == null) {
            return -1;
        }
        freqInc(node);
        return node.value;
    }

    //put分三种情况
    //1.key在cache中不存在,没有达到容量上限,freq值增加1,freqMap,cacheMap 新增(key,value),size++
    //2.key在cache中不存在,达到容量上限,获得最小频率的set,删除第一个元素,cache中也删除,
    //同时freqMap和cacheMap(key,value),size不变,因为删除了一个元素,添加了一个元素
    //3.key在cache中
    //步骤如下
    //3.1 更新value值
    //3.2 删除freqMap从对应key的set集合中删除元素,如果当前频率是最小值且list列表为空,min++
    //3.3. freq++
    //3.4 freqMap新增(key,value),更新cache中的value
    public void put(int key, int value) {
        Node node = cache.get(key);
        if (node == null) {
            Node n = new Node(key, value);
            if (size == capacity) {
                //移除最小容量的元素
                //移除map中的key
                LinkedHashSet<Node> set1 = freqMap.get(min);
                if (set1 != null) {
                    //获得列表的第一个元素
                    Node o = set1.iterator().next();
                    set1.remove(o);
                    cache.remove(o.key);
                }
                n.freq++;
                AddNode(n);
            } else {
                n.freq++;
                AddNode(n);
                size++;
                min = 1;
            }
        } else {
            node.value = value;
            //先从旧的set中删除node
            freqInc(node);
        }
    }

    public void freqInc(Node node) {
        LinkedHashSet<Node> set = freqMap.get(node.freq);
        //时间复杂度O(n) 需要自实现频次链表
        set.remove(node);
        if (node.freq == min && set.size() == 0) {
            min++;
        }
        node.freq++;
        AddNode(node);
    }

    public void AddNode(Node node) {
        LinkedHashSet<Node> set = freqMap.get(node.freq);
        if (set == null) {
            set = new LinkedHashSet<>();
        }
        set.add(node);
        freqMap.put(node.key, set);
        cache.put(node.key, node);
    }
}

两个hashmap,nodelist使用自实现的linkedlist的代码实现如下

package com.mashibing.my;

import java.util.HashMap;
import java.util.Map;

//双map+自实现linkedlist
public class LFUCache4 {
    class Node {
        int key;
        int value;
        int freq;
        Node pre;
        Node next;

        public Node(int key, int value) {
            this.key = key;
            this.value = value;
        }

        public Node() {
        }
    }

    class DoubleLinkedList<N> {
        Node head, tail;

        //初始化双向循环链表
        public DoubleLinkedList() {
            head = new Node();
            tail = new Node();
            head.next = tail;
            tail.pre = head;
        }

        //头插法,新加入的节点放在节点的头部,最久未访问的节点放在尾部
        public void addNode(Node n) {
            Node next = head.next;
            n.next = next;
            n.pre = head;
            head.next = n;
            next.pre = n;
        }

        public void deleteNode(Node n) {
            n.pre.next = n.next;
            n.next.pre = n.pre;
        }

        public boolean isEmpty() {
            return head.next == tail;
        }
    }

    //cache的key是默认的key
    Map<Integer, Node> cache = new HashMap<>();
    //freq的key是词频,相同词频的node链成一个双向链表
    Map<Integer, DoubleLinkedList<Node>> freqMap = new HashMap<>();
    //标记最小频率
    int min;
    //lFU最大容量
    int capacity;
    //当前容量
    int size;


    //    [2],[3,1],[2,1],[2,2],[4,4],[2]]
    public static void main(String[] args) {
        LFUCache4 lFUCache4 = new LFUCache4(2);
       lFUCache4.put(1, 1);
       lFUCache4.put(2, 2);
       // 返回 1
       System.out.println(lFUCache4.get(1));
       lFUCache4.put(3, 3);    // 去除 key 2
       // 返回 -1 (未找到key 2)
       System.out.println(lFUCache4.get(2));
       // 返回 3
       System.out.println(lFUCache4.get(3));
       lFUCache4.put(4, 4);    // 去除 key 1
       // 返回 -1 (未找到 key 1)
       System.out.println(lFUCache4.get(1));
       // 返回 3
       System.out.println(lFUCache4.get(3));
       // 返回 4
       System.out.println(lFUCache4.get(4));
    }


    public LFUCache4(int capacity) {
        this.capacity = capacity;
    }

    public int get(int key) {
        Node node = cache.get(key);
        if (node == null) {
            return -1;
        }
        //1.删除旧的freqmap中的node,freq++,新增freqMap中的node
        DoubleLinkedList<Node> list = freqMap.get(node.freq);
        list.deleteNode(node);
        if (node.freq == min && list.isEmpty()) {
            min++;
        }

        node.freq++;
        addNode(node);
        return node.value;
    }

    //put分三种情况
    //1.key在cache中不存在,没有达到容量上限,freq值增加1,freqMap,cacheMap 新增(key,value),size++
    //2.key在cache中不存在,达到容量上限,获得最小频率的set,删除第一个元素,cache中也删除,
    //同时freqMap和cacheMap(key,value),size不变,因为删除了一个元素,添加了一个元素
    //3.key在cache中
    //步骤如下
    //3.1 更新value值
    //3.2 删除freqMap从对应key的set集合中删除元素,如果当前频率是最小值且list列表为空,min++
    //3.3. freq++
    //3.4 freqMap新增(key,value),更新cache中的value
    public void put(int key, int value) {
        Node node = cache.get(key);
        if (node != null) {
            //1.更新value
            //2.删除旧的词频的list中node,freq++增加新node到新词频的list集合中(删除旧的词频,
            //如果旧词频是min,且list为空,更新min=min+1)
            //3.更新cache,size不变(因为删除一个,增加一个)
            node.value = value;
            DoubleLinkedList<Node> list = freqMap.get(node.freq);
            list.deleteNode(node);
            if (node.freq == min && list.isEmpty()) {
                min++;
            }
            node.freq++;
            addNode(node);
        } else {
            Node n = new Node(key, value);
            if (size == capacity) {
                //1.删除最小频率的node,更新对应的map
                //2.添加新node到freqmap,cache中
                DoubleLinkedList<Node> list = freqMap.get(min);
                Node pre = list.tail.pre;
                list.deleteNode(pre);
                if (pre.freq == min && list.isEmpty()) {
                    min++;
                }
                cache.remove(pre.key);
                size--;
            }
            n.freq++;
            addNode(n);
            size++;
            min = 1;
        }

    }

    public void addNode(Node node) {
        DoubleLinkedList<Node> list = freqMap.get(node.freq);
        if (list == null) {
            list = new DoubleLinkedList<>();
        }
        list.addNode(node);
        freqMap.put(node.freq, list);
        cache.put(node.key, node);
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/337427.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

为什么我们不再发明编程语言了?

上个世纪&#xff0c;数百种编程语言被发明出来&#xff0c;但是进入21世纪&#xff0c;当我们都进入互联网时代时&#xff0c;只剩那么寥寥几个了。 如果你翻一下TIOBE得编程语言排行榜&#xff0c;就会发现20年来&#xff0c;上蹿下跳的就是那几张老面孔&#xff1a;C , Java…

(片花)原汤话原食:从公共场所不知深浅的熊孩子聊聊边界感这事

点击文末“阅读原文”即可收听本期节目剪辑、音频 / 伊姐 编辑 / SandLiu 卷圈 监制 / 姝琦 文案 / 伊姐 产品统筹 / bobo 录音间 / 声湃轩天津站本期节目完整版请在各大音频平台搜索”原汤话原食“&#xff0c;找到原汤话原食栏目后订阅收听。刚刚过去的春节&#xff0c;许…

靓号管理(2)

表结构&#xff1a; 根据表结构的需求&#xff0c;在models.py中创建类。 主要是创建级别和默认级别 class PrettyNum(models):"""靓号表"""models models.CharField(verbose_name"手机号", max_length32)price models.IntegerFie…

MES系统智能工厂,搭上中国制造2025顺风车

MES在电子制造业中的应用日益广泛&#xff0c;越来越多的厂商已经购置或自行开发了MES&#xff0c;并将其作为“智能化工厂”。国内大大小小、各行各业都有上百个MES系统&#xff0c;还有很多的国外MES系统&#xff0c;怎么才能在MES系统公司中找到适合自己的MES&#xff1f;希…

数据库学习笔记(2)——workbench和SQL语言

1、workbench简介&#xff1a; 登录客户端的两种方法 在cmd中&#xff0c;只能通过sql语句控制数据库&#xff1b;workbench其实就是一种图形化数据库管理工具&#xff0c;在workbench中既可以通过sql语句控制数据库&#xff0c;也可以通过图形化界面控制数据库。通过workbenc…

LeetCode题解 动态规划(四):416 分割等和子集;1049 最后一块石头的重量 II

背包问题 下图将背包问题做了分类 其中之重点&#xff0c;是01背包&#xff0c;即一堆物件选哪样不选哪样放入背包里。难度在于&#xff0c;以前的状态转移&#xff0c;多只用考虑一个变量&#xff0c;比如爬楼梯的阶层&#xff0c;路径点的选择&#xff0c;这也是能用滚动数组…

ChatGPT 的未来挑战和风险

ChatGPT 是 OpenAI 开发的流行语言模型&#xff0c;彻底改变了我们与 AI 交互的方式。然而&#xff0c;随着像 ChatGPT 这样的语言模型的使用越来越广泛&#xff0c;重要的是要考虑它们未来可能面临的潜在风险和挑战。 一、数据质量和公平性 使用 ChatGPT 的主要风险之一是用于…

大数据-------元数据管理

一、什么是元数据 元数据就是描述数据的数据&#xff0c;它为企业的各类数据提供了上下文环境&#xff0c;使企业能够更好地了解、管理和使用数据。 现在数据对于公司的决策十分的重要&#xff0c;随着业务的发展&#xff0c;业务线会慢慢庞大起来&#xff0c;随着开发人员的…

九、STM32定时器讲解 - 通用定时器实战

目录 1.三种定时器的区别 2.通用定时器的特点描述 3.计数器模式 4.通用定时器工作过程 5.计数器时钟计算方法、 5.1定时器的输入时钟频率 - TimeClockFren 5.2计数器时钟计算方法 6.定时器相关寄存器 7.定时器库函数结构体 8.通用定时器函数 9.定时器中断配置过程 1…

图论算法:树上倍增法解决LCA问题

文章目录树上倍增法&#xff1a; LCA问题树上倍增法&#xff1a; LCA问题 树上倍增法用于求解LCA问题是一种非常有效的方法。 倍增是什么&#xff1f; 简单来说&#xff0c;倍增就是 1 2 4 8 16 … 2^k 可以发现倍增是呈 2的指数型递增的一类数据&#xff0c;和二分一样&…

黑马程序员 Linux 教程

目录Linux 简介不同应用领域主流操作系统Linux 系统历史Linux 系统版本Linux 安装安装方式网卡设置安装 SSH 连接工具使用 FinalShell 连接到 LinuxLinux 和 Windows 目录结构对比Linux 目录介绍Linux 常用命令Linux 命令初体验Linux 命令使用技巧Linux 命令格式文件目录操作命…

Python的文件编码,复制,缓冲,删除

能力有限&#xff0c;仅供参考 本篇博文是上一篇博文&#xff08; Python的文件读取&#xff0c;写入&#xff09;的后续&#xff0c;也是python文件管理的一部分&#xff0c;废话不多说&#xff0c;现在就开始。 1. 编码 在实际工作学习中&#xff0c;你可以遇到乱码的问题…

c语言递归 累和 ,累乘积,斐波那契数列,字符串长度

目录 递归使用场景 1:使用递归的方式计算 Sn123..100 2&#xff1a;计算 n&#xff01;n*(n-1)*(n-2)*......*1; 3:计算输出斐波那契数列前20项&#xff0c;并按每行4个数的格式输出(2019年&#xff09; 4&#xff1a; 用递归和非递归两种方式编写函数strlength()。该函数…

Nacos 入门微服务项目实战

Nacos 核心源码精讲 - IT贱男 - 掘金小册全方位源码精讲&#xff0c;深度剖析 Nacos 注册中心和配置中心的核心思想。「Nacos 核心源码精讲」由IT贱男撰写&#xff0c;375人购买https://s.juejin.cn/ds/BuC3Vs9/ Hi&#xff0c;大家好&#xff0c;欢迎大家来学习《Nacos 核心源…

2022黑马Redis跟学笔记.基础篇(一)

2022黑马Redis跟学笔记.基础篇 一1.Redis入门1.1.认识NoSQL1.1.1.结构化与非结构化1.1.2.关联和非关联1.1.3.查询方式1.1.4.事务1.1.5.总结1.2.认识Redis1.3.安装Redis步骤一&#xff1a;安装Redis依赖步骤二&#xff1a;上传安装包并解压步骤三&#xff1a;启动(1).默认启动(2…

开发微服务电商项目演示(三)

一&#xff0c;nginx动静分离第1步&#xff1a;通过SwitchHosts新增二级域名&#xff1a;images.zmall.com第2步&#xff1a;将本次项目的易买网所有静态资源js/css/images复制到nginx中的html目录下第3步&#xff1a;在nginx的核心配置文件nginx.conf中新增二级域名images.zma…

论文阅读:MINE: Towards Continuous Depth MPI with NeRF for Novel View Synthes

中文标题&#xff1a;基于连续深度多平面和神经辐射场的新视角合成 本文只介绍与NeRF原文不同的部分 创新点 对单一图像进行密集三维重建&#xff0c;完成新视角合成与深度估计的工作。从单个图像生成连续和遮挡绘制的三维重建。MINE借鉴NeRF可以生成连续的深度图像。 解决…

leaflet上传CSV文件,在地图上显示图形(示例代码054)

第054个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+leaflet中加载CSV文件,将图形显示在地图上。CSV(逗号分隔值)文件是一种简单且开放的文件格式,以纯文本形式存储表格数据。 几乎所有电子表格和数据库软件都可以导入/导出这种文件格式。 直接复制下面的 vue+ope…

Navicat无法连接MySQL报错1251的解决方法

日期&#xff1a;2023年2月10日 作者&#xff1a;Commas 签名&#xff1a;(ง •_•)ง 积跬步以致千里,积小流以成江海…… 注释&#xff1a;如果您觉得有所帮助&#xff0c;帮忙点个赞&#xff0c;也可以关注我&#xff0c;我们一起成长&#xff1b;如果有不对的地方&#xf…

C++中的智能指针

1.RAII 与引用计数了解 Objective-C/Swift 的程序员应该知道引用计数的概念。引用计数这种计数是为了防止内存泄露而产生的。 基本想法是对于动态分配的对象&#xff0c;进行引用计数&#xff0c;每当增加一次对同一个对象的引用&#xff0c;那么引用对象的引用计数就会增加一次…