如何实现LFU缓存(最近最少频率使用)
目录
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);// 返回 1System.out.println(cache.get(1));cache.put(3, 3); // 去除 key 2// 返回 -1 (未找到key 2)System.out.println(cache.get(2));// 返回 3System.out.println(cache.get(3));cache.put(4, 4); // 去除 key 1// 返回 -1 (未找到 key 1)System.out.println(cache.get(1));// 返回 3System.out.println(cache.get(3));// 返回 4System.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中的valuepublic void put(int key, int value) {Node node = cache.get(key);if (node == null) {Node n = new Node(key, value);if (size == capacity) {//移除最小容量的元素//移除map中的keyLinkedHashSet<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中删除nodefreqInc(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是默认的keyMap<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);// 返回 1System.out.println(lFUCache4.get(1));lFUCache4.put(3, 3); // 去除 key 2// 返回 -1 (未找到key 2)System.out.println(lFUCache4.get(2));// 返回 3System.out.println(lFUCache4.get(3));lFUCache4.put(4, 4); // 去除 key 1// 返回 -1 (未找到 key 1)System.out.println(lFUCache4.get(1));// 返回 3System.out.println(lFUCache4.get(3));// 返回 4System.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中的nodeDoubleLinkedList<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中的valuepublic 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);}
}
相关文章:

如何实现LFU缓存(最近最少频率使用)
目录 1.什么是LFU缓存? 2.LFU的使用场景有哪些? 3.LFU缓存的实现方式有哪些? 4.put/get 函数实现具体功能 1.什么是LFU缓存? LFU缓存是一个具有指定大小的缓存,随着添加元素的增加,达到容量的上限&…...

【C++之容器篇】精华:vector常见函数的接口的熟悉与使用
目录前言一、认识vector1. 介绍2. 成员类型二、默认成员函数(Member functions)1. 构造函数2. 拷贝构造函数vector (const vector& x);3. 析构函数4. 赋值运算符重载函数三、迭代器(Iterators)1. 普通对象的迭代器2. const对象…...

InstructGPT
文章目录Abstract 给定人类的命令,并且用人工标注想要的结果,构成数据集,使用监督学习来微调GPT-3。 然后,我们对模型输出进行排名,构成新的数据集,我们利用强化学习来进一步微调这个监督模型。 我们把产…...
RTOS之一环境搭建(基于TM4C123GXL)
硬件TM4C123GXLBOOSTXL-EDUMKII keil5micriumOSA软件安装:1 ARM-MDK(MDK538aMDK_Stellaris_ICDI_AddOn)MDK538a链接:https://www.keil.com/demo/eval/arm.htmICDI链接:https://documentation-service.arm.com/static/60509bd61da8f8344a2ca1b…...

151、【动态规划】AcWing ——2. 01背包问题:二维数组+一维数组(C++版本)
题目描述 原题链接:2. 01背包问题 解题思路 (1)二维dp数组 动态规划五步曲: (1)dp[i][j]的含义: 容量为j时,从物品1-物品i中取物品,可达到的最大价值 (2…...

DS期末复习卷(二)
选择题 1.下面关于线性表的叙述错误的是( D )。 (A) 线性表采用顺序存储必须占用一片连续的存储空间 (B) 线性表采用链式存储不必占用一片连续的存储空间 © 线性表采用链式存储便于插入和删除操作的实现 (D) 线性表采用顺序存储便于插…...

大数据技术架构(组件)31——Spark:Optimize--->JVM On Compute
2.1.9.4、Optimize--->JVM On Compute首要的一个问题就是GC,那么先来了解下其原理:1、内存管理其实就是对象的管理,包括对象的分配和释放,如果显式的释放对象,只要把该对象赋值为null,即该对象变为不可达.GC将负责回…...

ETL基础概念及要求详解
ETL基础概念及要求详解概念ETL与ELT数据湖与数据仓库ETL应用场景ETL具体流程及操作要求抽取清洗转换加载ETL设计模式SQL脚本语言ETL工具设计ETL工具SQLETL接口设计要求明确接口属性约定接口形式确定接口抽取方法规范接口格式概念 ETL即Extract(抽取)Tra…...
刷题记录:牛客NC23054华华开始学信息学 线段树+分块
传送门:牛客 题目描述: 题目latex公式较多,此处省略 输入: 10 6 1 1 1 2 4 6 1 3 2 2 5 7 1 6 10 2 1 10 输出: 3 5 26这道题让我体验到的线段树相对于树状数组的常数巨大 我们倘若直接用单点修改的话,如果D过小比如1那么我们足足要加n次,时间复杂度爆…...

二叉搜索树(查找,插入,删除)
目录 1.概念 2.性质 3.二叉搜索树的操作 1.查找 2.插入 3.删除(难点) 1.概念 二叉搜索树又称二叉排序树.利用中序遍历它就是一个有顺序的一组数. 2.性质 1.若它的左子树不为空,则左子树上所有节点的值都小于根节点的值 2.若它的右子树不为空,则右子树上所有节点的值都…...
C# PictureEdit 加载图片
方法一: 如果要加载的图片的长宽比不是太过失衡, 1.可以改变picturebox的SizeMode属性为 PictureBoxSizeMode.StretchImage, 2.或者Dev控件 PictureEdit的SizeMode属性为Zoom。(zoom:缩放;clip剪短;stret…...

3种方法设置PDF“打开密码”,总有一种适合你
PDF文件是我们工作中经常用到的文件之一,对于重要的文件,设置“打开密码”是一种很好的保护方式。下面就来说说,设置PDF“打开密码”有哪三种方法? 方法一:在线网站加密 市面上有很多可以直接在线上加密PDF文件的产品…...
第三章 数据链路层(点到点的传输服务)-计算机网络(笔记)
计算机网络 第三章 数据链路层(点到点的传输服务) 数据链路层属于计算机网络的低层。数据链路层使用的信道主要有以下两种类型: (1)点到点信道。这种信道使用一对一的点到点通信方式。 (2)广…...
volatile关键字与CAS机制
volatile关键字 volatile关键字可以对类的成员变量与静态变量进行修饰 volatile关键字的作用 1.保证被修饰属性的可见性,被修饰后的属性如果被更改后其他线程是会立即可见的 2.保证被修饰属性的有序性,被修饰后的属性禁止修改指令执行的顺序 注意:volatile关键字不能保证属性…...

LeetCode题解 动态规划(四):416 分割等和子集;1049 最后一块石头的重量 II
背包问题 下图将背包问题做了分类 其中之重点,是01背包,即一堆物件选哪样不选哪样放入背包里。难度在于,以前的状态转移,多只用考虑一个变量,比如爬楼梯的阶层,路径点的选择,这也是能用滚动数组…...

【FFMPEG源码分析】从ffplay源码摸清ffmpeg框架(二)
demux模块 从前面一篇文章中可以得知,demux模块的使用方法大致如下: 分配AVFormatContext通过avformat_open_input(…)传入AVFormatContext指针和文件路径,启动demux通过av_read_frame(…) 从AVFormatContext中读取demux后的audio/video/subtitle数据包…...

PCIE 学习笔记(入门简介)
PCIE 学习笔记书到用时方恨少啊,一年前学PCIE的笔记,再拿出来瞅瞅。发到博客上,方便看。PCIE基础PCIE和PCI的不同PCIE采用差分信号传输,并且是dual-simplex传输——每条lane上有TX通道和RX通道,所以每条lane上的信号是…...

锁的优化机制了解嘛?请进!
点个关注,必回关 文章目录自旋锁:自适应锁:锁消除:锁粗化:偏向锁:轻量级锁:从JDK1.6版本之后,synchronized本身也在不断优化锁的机制,有些情况下他并不会是一个很重量级的…...

5.点赞功能 Redis
Redis(1)简介Redis 是一个高性能的 key-value 数据库原子 – Redis的所有操作都是原子性的。多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来。非关系形数据库数据全部存在内存中,性能高。(2&#…...
Java序列化和反序列化(详解)
一、理解Java序列化和反序列化 Serialization(序列化):将java对象以一连串的字节保存在磁盘文件中的过程,也可以说是保存java对象状态的过程。序列化可以将数据永久保存在磁盘上(通常保存在文件中)。 deserialization(反序列化):将保存在磁…...

深度学习在微纳光子学中的应用
深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向: 逆向设计 通过神经网络快速预测微纳结构的光学响应,替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…...

1.3 VSCode安装与环境配置
进入网址Visual Studio Code - Code Editing. Redefined下载.deb文件,然后打开终端,进入下载文件夹,键入命令 sudo dpkg -i code_1.100.3-1748872405_amd64.deb 在终端键入命令code即启动vscode 需要安装插件列表 1.Chinese简化 2.ros …...

Mac软件卸载指南,简单易懂!
刚和Adobe分手,它却总在Library里给你写"回忆录"?卸载的Final Cut Pro像电子幽灵般阴魂不散?总是会有残留文件,别慌!这份Mac软件卸载指南,将用最硬核的方式教你"数字分手术"࿰…...
【Web 进阶篇】优雅的接口设计:统一响应、全局异常处理与参数校验
系列回顾: 在上一篇中,我们成功地为应用集成了数据库,并使用 Spring Data JPA 实现了基本的 CRUD API。我们的应用现在能“记忆”数据了!但是,如果你仔细审视那些 API,会发现它们还很“粗糙”:有…...

Java面试专项一-准备篇
一、企业简历筛选规则 一般企业的简历筛选流程:首先由HR先筛选一部分简历后,在将简历给到对应的项目负责人后再进行下一步的操作。 HR如何筛选简历 例如:Boss直聘(招聘方平台) 直接按照条件进行筛选 例如:…...
rnn判断string中第一次出现a的下标
# coding:utf8 import torch import torch.nn as nn import numpy as np import random import json""" 基于pytorch的网络编写 实现一个RNN网络完成多分类任务 判断字符 a 第一次出现在字符串中的位置 """class TorchModel(nn.Module):def __in…...

Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习)
Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习) 一、Aspose.PDF 简介二、说明(⚠️仅供学习与研究使用)三、技术流程总览四、准备工作1. 下载 Jar 包2. Maven 项目依赖配置 五、字节码修改实现代码&#…...

搭建DNS域名解析服务器(正向解析资源文件)
正向解析资源文件 1)准备工作 服务端及客户端都关闭安全软件 [rootlocalhost ~]# systemctl stop firewalld [rootlocalhost ~]# setenforce 0 2)服务端安装软件:bind 1.配置yum源 [rootlocalhost ~]# cat /etc/yum.repos.d/base.repo [Base…...
MySQL 8.0 事务全面讲解
以下是一个结合两次回答的 MySQL 8.0 事务全面讲解,涵盖了事务的核心概念、操作示例、失败回滚、隔离级别、事务性 DDL 和 XA 事务等内容,并修正了查看隔离级别的命令。 MySQL 8.0 事务全面讲解 一、事务的核心概念(ACID) 事务是…...

从零开始了解数据采集(二十八)——制造业数字孪生
近年来,我国的工业领域正经历一场前所未有的数字化变革,从“双碳目标”到工业互联网平台的推广,国家政策和市场需求共同推动了制造业的升级。在这场变革中,数字孪生技术成为备受关注的关键工具,它不仅让企业“看见”设…...