当前位置: 首页 > news >正文

LRU、LFU 内存淘汰算法的设计与实现

1、背景介绍

LRU、LFU都是内存管理淘汰算法,内存管理是计算机技术中重要的一环,也是多数操作系统中必备的模块。应用场景:假设 给定你一定内存空间,需要你维护一些缓存数据,LRU、LFU就是在内存已经满了的情况下,如果再次添加新的数据,该淘汰哪些数据来留出新数据的内存空间???

2、LRU(least recently used)

LRU(east recently used),即最近最少使用 ,也就是说 在内存满的情况下,将会淘汰很久都没有使用过的数据。例如 leetcode 146题。

需求:现在需要设计一个算法,使得插入新数据、获取已经存入的数据,使得平均时间复杂度O(1)。

设计思路:因为插入、获取数据时,都会更新时间戳,还需要使得平均时间复杂度O(1),可以使用双链表+哈希表的方式来存储。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如上图,哈希表里存储 键与双链表节点,双链表的head表示最近使用过的数据,tail表示很久都没使用过的数据。

  • 插入数据时,在哈希表建立key与Node节点,然后把Node节点插入双链表的头部位置。

  • 获取数据时,从哈希表拿到Node节点,然后把Node节点从双链表中分离出来,插入双链表的头部位置即可。

    在这里插入图片描述

以上两个步骤,时间复杂度都是O(1)。所以符合题意。

代码如下:

// lc146题 直接能过的代码
class LRUCache {// LRU,最近最少使用// 用双链表节点包装进行连接private HashMap<Integer, Node> map; // 哈希表,存储key与双链表节点private int maxSize; // 缓存的最大容量private Node head, tail; // 头部是最近使用的,尾部 是很久没使用的private static class Node { // 双链表节点int key, val;Node left, right;public Node(int k, int v) {key = k;val = v;}}public LRUCache(int capacity) {map = new HashMap<>();maxSize = capacity;}public int get(int key) {if(!map.containsKey(key)) return -1;Node node = map.get(key);if (node == head) return node.val;apart(node); //分离出node节点node.right = head; // 插入到双链表的头部位置head.left = node;head = node;return node.val;}public void put(int key, int value) {if (!map.containsKey(key)) {Node node = new Node(key, value);node.right = head;if (head != null) head.left = node;if (tail == null) tail = node;head = node;map.put(key, node);if (map.size() > maxSize) { // 容量超了,删除双链表的尾部元素if (tail.left != null) {tail.left.right = null;}map.remove(tail.key);Node pre = tail.left;tail.left = null;tail = pre;}} else {Node node = map.get(key);node.val = value;get(key); // 调用get,使其向前移动}}// node的左右两边连接,将node抽离出private void apart(Node node) {node.left.right = node.right;if (node.right != null) {node.right.left = node.left;}if (node == tail) {tail = node.left;}node.left = node.right = null;}
}/*** Your LRUCache object will be instantiated and called as such:* LRUCache obj = new LRUCache(capacity);* int param_1 = obj.get(key);* obj.put(key,value);*/

3、LFU(least frequancy used)

LFU(least frequancy used), 即不常用算法,按照每个数据的访问次数来判断数据的使用情况。如果一个数据在近一段时间内没有被访问或者被访问的可能性小,则会被淘汰。(简单点说,就是按照“使用频率”来分级的)例题:leetcode 460题

需求:现在需要设计一个算法,使得插入、获取数据的平均时间复杂度O(1)。

设计思路:按照使用频率进行划分,相同频率的数据放在同一个“桶”内,从左往右频率逐渐升高;而桶内部是从上往下,按照插入桶内的时间来排序,新插入的节点在桶的顶部,很久之前插入的节点在桶的底部,如下图所示:

在这里插入图片描述

注意:当内存满了的时候,会删除 频率最低的桶内,最后的一个数据节点

代码实现如下:

public class LFUCache {public LFUCache(int K) {capacity = K;size = 0;records = new HashMap<>();heads = new HashMap<>();headList = null;}private int capacity; // 缓存的大小限制,即Kprivate int size; // 缓存目前有多少个节点private HashMap<Integer, Node> records;// 表示key(Integer)由哪个节点(Node)代表private HashMap<Node, NodeList> heads; // 表示节点(Node)在哪个桶(NodeList)里private NodeList headList; // 整个结构中位于最左的桶// 节点的数据结构public static class Node {public Integer key;public Integer value;public Integer times; // 这个节点发生get或者set的次数总和public Node up; // 节点之间是双向链表所以有上一个节点public Node down;// 节点之间是双向链表所以有下一个节点public Node(int k, int v, int t) {key = k;value = v;times = t;}}// 桶结构public static class NodeList {public Node head; // 桶的头节点public Node tail; // 桶的尾节点public NodeList last; // 桶之间是双向链表所以有前一个桶public NodeList next; // 桶之间是双向链表所以有后一个桶public NodeList(Node node) {head = node;tail = node;}// 把一个新的节点加入这个桶,新的节点都放在顶端变成新的头部public void addNodeFromHead(Node newHead) {newHead.down = head;head.up = newHead;head = newHead;}// 判断这个桶是不是空的public boolean isEmpty() {return head == null;}// 删除node节点并保证node的上下环境重新连接public void deleteNode(Node node) {if (head == tail) {head = null;tail = null;} else {if (node == head) {head = node.down;head.up = null;} else if (node == tail) {tail = node.up;tail.down = null;} else {node.up.down = node.down;node.down.up = node.up;}}node.up = null;node.down = null;}}// removeNodeList:刚刚减少了一个节点的桶// 这个函数的功能是,判断刚刚减少了一个节点的桶是不是已经空了。// 1)如果不空,什么也不做//// 2)如果空了,removeNodeList还是整个缓存结构最左的桶(headList)。// 删掉这个桶的同时也要让最左的桶变成removeNodeList的下一个。//// 3)如果空了,removeNodeList不是整个缓存结构最左的桶(headList)。// 把这个桶删除,并保证上一个的桶和下一个桶之间还是双向链表的连接方式//// 函数的返回值表示刚刚减少了一个节点的桶是不是已经空了,空了返回true;不空返回falseprivate boolean modifyHeadList(NodeList removeNodeList) {if (removeNodeList.isEmpty()) {if (headList == removeNodeList) {headList = removeNodeList.next;if (headList != null) {headList.last = null;}} else {removeNodeList.last.next = removeNodeList.next;if (removeNodeList.next != null) {removeNodeList.next.last = removeNodeList.last;}}return true;}return false;}// 函数的功能// node这个节点的次数+1了,这个节点原来在oldNodeList里。// 把node从oldNodeList删掉,然后放到次数+1的桶中// 整个过程既要保证桶之间仍然是双向链表,也要保证节点之间仍然是双向链表private void move(Node node, NodeList oldNodeList) {oldNodeList.deleteNode(node);// preList表示次数+1的桶的前一个桶是谁// 如果oldNodeList删掉node之后还有节点,oldNodeList就是次数+1的桶的前一个桶// 如果oldNodeList删掉node之后空了,oldNodeList是需要删除的,所以次数+1的桶的前一个桶,是oldNodeList的前一个NodeList preList = modifyHeadList(oldNodeList) ? oldNodeList.last : oldNodeList;// nextList表示次数+1的桶的后一个桶是谁NodeList nextList = oldNodeList.next;if (nextList == null) {NodeList newList = new NodeList(node);if (preList != null) {preList.next = newList;}newList.last = preList;if (headList == null) {headList = newList;}heads.put(node, newList);} else {if (nextList.head.times.equals(node.times)) {nextList.addNodeFromHead(node);heads.put(node, nextList);} else {NodeList newList = new NodeList(node);if (preList != null) {preList.next = newList;}newList.last = preList;newList.next = nextList;nextList.last = newList;if (headList == nextList) {headList = newList;}heads.put(node, newList);}}}public void put(int key, int value) {if (capacity == 0) {return;}if (records.containsKey(key)) {Node node = records.get(key);node.value = value;node.times++;NodeList curNodeList = heads.get(node);move(node, curNodeList);} else {if (size == capacity) {Node node = headList.tail;headList.deleteNode(node);modifyHeadList(headList);records.remove(node.key);heads.remove(node);size--;}Node node = new Node(key, value, 1);if (headList == null) {headList = new NodeList(node);} else {if (headList.head.times.equals(node.times)) {headList.addNodeFromHead(node);} else {NodeList newList = new NodeList(node);newList.next = headList;headList.last = newList;headList = newList;}}records.put(key, node);heads.put(node, headList);size++;}}public int get(int key) {if (!records.containsKey(key)) {return -1;}Node node = records.get(key);node.times++;NodeList curNodeList = heads.get(node);move(node, curNodeList);return node.value;}
}

相关文章:

LRU、LFU 内存淘汰算法的设计与实现

1、背景介绍 LRU、LFU都是内存管理淘汰算法&#xff0c;内存管理是计算机技术中重要的一环&#xff0c;也是多数操作系统中必备的模块。应用场景&#xff1a;假设 给定你一定内存空间&#xff0c;需要你维护一些缓存数据&#xff0c;LRU、LFU就是在内存已经满了的情况下&#…...

常用工具使用

ubuntu 1.1 ubuntu与windows 互相复制问题 方法一、 打开虚拟机 &#xff0c;点击上方导航栏 ‘虚拟机’ 查看VMware Tools是否安装&#xff0c;安装即可 方法二、 apt-get autoremove open-vm-tools apt-get install open-vm-tools apt-get install open-vm-tools-desktop…...

HashMap源码解析_jdk1.8(一)

HashMap解析 HashMap源码解析_jdk1.8&#xff08;一&#xff09;哈希常用数据结构查找/插入/删除性能比较。哈希冲突 HashMap的数据结构HashMap相关变量size和capacity HashMap源码解析_jdk1.8&#xff08;一&#xff09; 哈希 Hash&#xff0c;一般翻译做“散列”&#xff0…...

Android最好用的日志打印库(自动追踪日志代码位置)

给大家推荐一个自己写的日志打印的库&#xff0c;我愿称之为最强日志打印库&#xff1a;BytUtilLog Byt是Big一统的缩写&#xff0c;大一统日志打印库&#xff0c;哈哈&#xff01;搞个笑&#xff0c;很早就写好了&#xff0c;但后面忙起来就忘了写一篇文章推一下它了&#xff…...

面试官的哪些举动,暗示你通过了面试?

其实在求职过程中都会发现&#xff0c;求职者面试时间一般在20分钟以上&#xff0c;如果求职者较多&#xff0c;可能会在10分钟左右。(会在介绍以往工作上减少时间&#xff0c;内容主要以简单介绍&#xff0c;薪资要求&#xff0c;能力评价&#xff0c;到岗时间等等) 拿面试时…...

​旅行季《乡村振兴战略下传统村落文化旅游设计》许少辉八一新著想象和世界一样宽广

​旅行季《乡村振兴战略下传统村落文化旅游设计》许少辉八一新著想象和世界一样宽广...

Linux学习第19天:Linux并发与竞争实例: 没有规矩不成方圆

Linux版本号4.1.15 芯片I.MX6ULL 大叔学Linux 品人间百味 思文短情长 先说点题外话&#xff0c;上周参加行业年会&#xff0c;停更了一周。接下来的周五就要开启国庆中秋双节模式&#xff0c;所以有的时候&#xff0c;尤其是工作以后…...

Unity添加自定义菜单按钮

如果你想在Unity编辑器中添加自定义菜单按钮&#xff0c;你可以使用Unity的MenuSystem API。这是一个简单的示例&#xff1a; 首先需要引用using UnityEditor; using UnityEngine; using UnityEditor; 两个命名空间 然后在方法前添加 [MenuItem("原菜单名/自定义名…...

PHP8的类与对象的基本操作之类的实例化-PHP8知识详解

定义完类和方法后&#xff0c;并不是真正创建一个对象。类和对象可以描述为如下关系。类用来描述具有相同数据结构和特征的“一组对象”&#xff0c;“类”是“对象”的抽象&#xff0c;而“对象”是“类”的具体实例&#xff0c;即一个类中的对象具有相同的“型”&#xff0c;…...

C/S架构学习之TCP服务器

TCP服务器的实现流程&#xff1a;一、创建套接字&#xff08;socket函数&#xff09;&#xff1a;通信域选择IPV4网络协议、套接字类型选择流式&#xff1b; int sockfd socket(AF_INET,SOCK_STREAM,0); //通信域选择IPV4、套接字类型选择流式二、填充服务器的网络信息结构体&…...

基于微信小程序的线上教育课程付费商城(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作…...

Linux基础指令(五)

目录 前言1. 打包和压缩1.1 是什么1.2 为什么1.3 怎么办&#xff1f; 2. zip & unzip3. tar 指令结语&#xff1a; 前言 欢迎各位伙伴来到学习 Linux 指令的 第五天&#xff01;&#xff01;&#xff01; 在上一篇文章 Linux基本指令(四) 当中&#xff0c;我们学习了 fin…...

C语言结构体的一些鲜为人知的小秘密

目录 一、结构体内存对齐规则&#xff1a; 1.1范例 1.2结构体内存对齐规则 1.3自定义默认对齐数 二、位段 2.1什么是位段 2.2位段的内存分配 2.3位段的不足 三、枚举和联合体 3.1枚举 3.1.1枚举类型的定义 3.1.2枚举类型的使用 3.2联合体 3.2.1联合体的定义 3.…...

kubernetes问题(一)-探究Pod被驱逐的原因及解决方法

1 k8s evicted是什么 k8s evicted是Kubernetes中的一个组件&#xff0c;主要用于处理Pod驱逐的情况。在Kubernetes中&#xff0c;当Node节点资源不够用时&#xff0c;为了保证整个集群的运行稳定&#xff0c;会按照一定的优先级和策略将其中的Pod驱逐出去。这时就需要一个组件…...

论文速览【序列模型 seq2seq】—— 【Ptr-Net】Pointer Networks

标题&#xff1a;Pointer Networks文章链接&#xff1a;Pointer Networks参考代码&#xff08;非官方&#xff09;&#xff1a;keon/pointer-networks发表&#xff1a;NIPS 2015领域&#xff1a;序列模型&#xff08;RNN seq2seq&#xff09;改进 / 深度学习解决组合优化问题【…...

Denoising diffusion implicit models 阅读笔记

Denoising diffusion probabilistic models (DDPMs)从马尔科夫链中采样生成样本&#xff0c;需要迭代多次&#xff0c;速度较慢。Denoising diffusion implicit models (DDIMs)的提出是为了加速采样过程&#xff0c;减少迭代的次数&#xff0c;并且要求DDIM可以复用DDPM训练的网…...

【Java 基础篇】Executors工厂类详解

在多线程编程中&#xff0c;线程池是一项重要的工具&#xff0c;它可以有效地管理和控制线程的生命周期&#xff0c;提高程序的性能和可维护性。Java提供了java.util.concurrent包来支持线程池的创建和管理&#xff0c;而Executors工厂类是其中的一部分&#xff0c;它提供了一些…...

SpringBoot MongoDB操作封装

1.引入Jar包 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-mongodb</artifactId></dependency> 2.MongoDbHelper操作 /*** MongoDB Operation class* author Mr.Li* date 2022-12-05*…...

PyTorch 模型性能分析和优化 — 第 1 部分

一、说明 这篇文章的重点将是GPU上的PyTorch培训。更具体地说&#xff0c;我们将专注于 PyTorch 的内置性能分析器 PyTorch Profiler&#xff0c;以及查看其结果的方法之一&#xff0c;即 PyTorch Profiler TensorBoard 插件。 二、深度框架 训练深度学习模型&#xff0c;尤其是…...

Unity3D 简易音频管理器

依赖于Addressable 依赖于单例模板&#xff1a;传送门 using System.Collections.Generic; using System.Security.Cryptography; using System; using UnityEngine; using UnityEngine.AddressableAssets;namespace EasyAVG {public class AudioManager : MonoSingleton<…...

别再乱接线了!12V手电钻保护板(B+/B-/B1/B2)保姆级接线图解,附万用表检测电池坏点技巧

12V手电钻保护板接线全攻略&#xff1a;从原理到实战的安全操作指南 面对手电钻保护板上密密麻麻的接线端子&#xff0c;即使是经验丰富的DIY爱好者也难免感到困惑。B、B-、B1、B2这些看似简单的标记背后&#xff0c;实际上隐藏着锂电池组安全工作的关键机制。本文将带您深入理…...

告别死记硬背!用Python+NumPy图解机器学习中的矩阵求导(附常见公式速查表)

告别死记硬背&#xff01;用PythonNumPy图解机器学习中的矩阵求导&#xff08;附常见公式速查表&#xff09; 在机器学习和深度学习的实践中&#xff0c;矩阵求导是理解反向传播、优化算法等核心概念的关键数学工具。然而&#xff0c;传统的数学教材往往以抽象符号和理论推导为…...

完整指南:如何通过JiYuTrainer高效解除极域电子教室限制

完整指南&#xff1a;如何通过JiYuTrainer高效解除极域电子教室限制 【免费下载链接】JiYuTrainer 极域电子教室防控制软件, StudenMain.exe 破解 项目地址: https://gitcode.com/gh_mirrors/ji/JiYuTrainer JiYuTrainer是一款专业级的极域电子教室破解工具&#xff0c;…...

AI技术岗机器学习工程师要晋升CTO需要经历哪些职位?各职位年限和薪资?

从机器学习工程师 → CTO 的标准晋升链&#xff0c;含每级任职年限 2026 年真实年薪区间&#xff08;含期权 / 签字费&#xff0c;北上深 AI 大厂 / 独角兽口径&#xff09;。 一、初级阶段&#xff08;纯技术&#xff0c;0–5 年&#xff09; 1&#xff09;机器学习工程师&…...

LinkSwift:2025年开源网盘直链下载助手的完整指南

LinkSwift&#xff1a;2025年开源网盘直链下载助手的完整指南 【免费下载链接】Online-disk-direct-link-download-assistant 一个基于 JavaScript 的网盘文件下载地址获取工具。基于【网盘直链下载助手】修改 &#xff0c;支持 百度网盘 / 阿里云盘 / 中国移动云盘 / 天翼云盘…...

OpenSTA静态时序分析引擎技术深度解析:开源时序验证核心架构揭秘

OpenSTA静态时序分析引擎技术深度解析&#xff1a;开源时序验证核心架构揭秘 【免费下载链接】OpenSTA OpenSTA engine 项目地址: https://gitcode.com/gh_mirrors/op/OpenSTA OpenSTA作为一款开源的静态时序分析引擎&#xff0c;为数字集成电路设计提供了工业级的时序验…...

VBS转VBE不只是加密:聊聊Scripting.Encoder的‘黑历史’与现代替代方案

VBS转VBE&#xff1a;从Scripting.Encoder的兴衰到现代脚本保护方案 在Windows脚本技术的发展长河中&#xff0c;VBScript&#xff08;VBS&#xff09;曾经是自动化任务和系统管理的重要工具。而与之相伴的VBE&#xff08;VBScript Encoded&#xff09;格式&#xff0c;则承载着…...

Illustrator智能替换工具:如何让批量设计工作变得轻松高效

Illustrator智能替换工具&#xff1a;如何让批量设计工作变得轻松高效 【免费下载链接】illustrator-scripts Adobe Illustrator scripts 项目地址: https://gitcode.com/gh_mirrors/il/illustrator-scripts 你是否曾经面对过这样的场景&#xff1a;客户要求将50个物料中…...

让老游戏在现代Windows上重获新生:DDrawCompat使用完全指南

让老游戏在现代Windows上重获新生&#xff1a;DDrawCompat使用完全指南 【免费下载链接】DDrawCompat DirectDraw and Direct3D 1-7 compatibility, performance and visual enhancements for Windows Vista, 7, 8, 10 and 11 项目地址: https://gitcode.com/gh_mirrors/dd/D…...

Flowable 6.7.2 适配达梦数据库踩坑实录:从驱动到Liquibase源码修改全攻略

Flowable 6.7.2 深度适配达梦数据库实战指南&#xff1a;从驱动配置到源码级改造 在国产化替代浪潮中&#xff0c;数据库迁移往往是技术团队面临的首要挑战。当工作流引擎Flowable遇上国产数据库达梦(DM)&#xff0c;两者的"语言不通"会导致一系列兼容性问题。本文将…...