LRU 算法

LRU 缓存淘汰算法就是一种常用策略。LRU 的全称是 Least Recently Used,也就是说我们认为最近使用过的数据应该是是「有用的」,很久都没用过的数据应该是无用的,内存满了就优先删那些很久没用过的数据。

力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台 LRU算法题

一、LRU 算法描述

首先要接收一个 capacity 参数作为缓存的最大容量,然后实现两个 API,一个是 put(key, val) 方法存入键值对,另一个是 get(key) 方法获取 key 对应的 val,如果 key 不存在则返回 -1。

注意getput 方法必须都是 O(1) 的时间复杂度,我们举个具体例子来看看 LRU 算法怎么工作

/* 缓存容量为 2 */
LRUCache cache = new LRUCache(2);
// 你可以把 cache 理解成一个队列
// 假设左边是队头,右边是队尾
// 最近使用的排在队头,久未使用的排在队尾
// 圆括号表示键值对 (key, val)cache.put(1, 1);
// cache = [(1, 1)]cache.put(2, 2);
// cache = [(2, 2), (1, 1)]cache.get(1);       // 返回 1
// cache = [(1, 1), (2, 2)]
// 解释:因为最近访问了键 1,所以提前至队头
// 返回键 1 对应的值 1cache.put(3, 3);
// cache = [(3, 3), (1, 1)]
// 解释:缓存容量已满,需要删除内容空出位置
// 优先删除久未使用的数据,也就是队尾的数据
// 然后把新的数据插入队头cache.get(2);       // 返回 -1 (未找到)
// cache = [(3, 3), (1, 1)]
// 解释:cache 中不存在键为 2 的数据cache.put(1, 4);    
// cache = [(1, 4), (3, 3)]
// 解释:键 1 已存在,把原始值 1 覆盖为 4
// 不要忘了也要将键值对提前到队头

二、LRU 算法设计

分析上面的操作过程,要让 putget 方法的时间复杂度为 O(1),我们可以总结出 cache 这个数据结构必要的条件:

1、显然 cache 中的元素必须有时序,以区分最近使用的和久未使用的数据,当容量满了之后要删除最久未使用的那个元素腾位置。

2、我们要在 cache 中快速找某个 key 是否已存在并得到对应的 val

3、每次访问 cache 中的某个 key,需要将这个元素变为最近使用的,也就是说 cache 要支持在任意位置快速插入和删除元素。

那么,什么数据结构同时符合上述条件呢?哈希表查找快,但是数据无固定顺序;链表有顺序之分,插入删除快,但是查找慢。所以结合一下,形成一种新的数据结构:哈希链表 LinkedHashMap

LRU 缓存算法的核心数据结构就是哈希链表,双向链表和哈希表的结合体。这个数据结构长这样

 借助这个结构,我们来逐一分析上面的 3 个条件:

1、如果我们每次默认从链表尾部添加元素,那么显然越靠尾部的元素就是最近使用的,越靠头部的元素就是最久未使用的。

2、对于某一个 key,我们可以通过哈希表快速定位到链表中的节点,从而取得对应 val

3、链表显然是支持在任意位置快速插入和删除的,改改指针就行。只不过传统的链表无法按照索引快速访问某一个位置的元素,而这里借助哈希表,可以通过 key 快速映射到任意一个链表节点,然后进行插入和删除。

三、代码实现

我们把双链表的节点类写出来,为了简化,keyval 都认为是 int 类型

class Node {public int key, val;public Node next, prev;public Node(int k, int v) {this.key = k;this.val = v;}
}

然后依靠我们的 Node 类型构建一个双链表,实现几个 LRU 算法必须的 API:

class DoubleList {  // 头尾虚节点private Node head, tail;  // 链表元素数private int size;public DoubleList() {// 初始化双向链表的数据head = new Node(0, 0);tail = new Node(0, 0);head.next = tail;tail.prev = head;size = 0;}// 在链表尾部添加节点 x,时间 O(1)public void addLast(Node x) {x.prev = tail.prev;x.next = tail;tail.prev.next = x;tail.prev = x;size++;}// 删除链表中的 x 节点(x 一定存在)// 由于是双链表且给的是目标 Node 节点,时间 O(1)public void remove(Node x) {x.prev.next = x.next;x.next.prev = x.prev;size--;}// 删除链表中第一个节点,并返回该节点,时间 O(1)public Node removeFirst() {if (head.next == tail)return null;Node first = head.next;remove(first);return first;}// 返回链表长度,时间 O(1)public int size() { return size; }}

为什么必须要用双向链表

因为我们需要删除操作。删除一个节点不光要得到该节点本身的指针,也需要操作其前驱节点的指针,而双向链表才能支持直接查找前驱,保证操作的时间复杂度 O(1)。

注意我们实现的双链表 API 只能从尾部插入,也就是说靠尾部的数据是最近使用的,靠头部的数据是最久未使用的

有了双向链表的实现,我们只需要在 LRU 算法中把它和哈希表结合起来即可,先搭出代码框架:

class LRUCache {// key -> Node(key, val)private HashMap<Integer, Node> map;// Node(k1, v1) <-> Node(k2, v2)...private DoubleList cache;// 最大容量private int cap;public LRUCache(int capacity) {this.cap = capacity;map = new HashMap<>();cache = new DoubleList();}

删除时候既要删除key也要删除node

class LRUCache {// 为了节约篇幅,省略上文给出的代码部分.../* 将某个 key 提升为最近使用的 */private void makeRecently(int key) {Node x = map.get(key);// 先从链表中删除这个节点cache.remove(x);// 重新插到队尾cache.addLast(x);}/* 添加最近使用的元素 */private void addRecently(int key, int val) {Node x = new Node(key, val);// 链表尾部就是最近使用的元素cache.addLast(x);// 别忘了在 map 中添加 key 的映射map.put(key, x);}/* 删除某一个 key */private void deleteKey(int key) {Node x = map.get(key);// 从链表中删除cache.remove(x);// 从 map 中删除map.remove(key);}/* 删除最久未使用的元素 */private void removeLeastRecently() {// 链表头部的第一个元素就是最久未使用的Node deletedNode = cache.removeFirst();// 同时别忘了从 map 中删除它的 keyint deletedKey = deletedNode.key;map.remove(deletedKey);}public int get(int key) {if (!map.containsKey(key)) {return -1;}// 将该数据提升为最近使用的makeRecently(key);return map.get(key).val;}public void put(int key, int val) {if (map.containsKey(key)) {// 删除旧的数据deleteKey(key);// 新插入的数据为最近使用的数据addRecently(key, val);return;}if (cap == cache.size()) {// 删除最久未使用的元素removeLeastRecently();}// 添加为最近使用的元素addRecently(key, val);}
}

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

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

相关文章

自动方向识别式 TXB型电平转换芯片

大家好,这里是大话硬件。 在上一篇文章分析了LSF型的电平转换芯片,LSF型电平转换芯片最常见是应用在I2C总线上。I2C为OD型总线,LSF使用时增加电阻。 对于不是OD型总线的电平转换,比如UART,SPI,普通GPIO口信号,这些信号在进行双向电平转换使用什么样的芯片呢? 从上面…

面试之快速学习STL-deuqe和list

1. deque deque 容器用数组&#xff08;数组名假设为 map&#xff09;存储着各个连续空间的首地址。也就是说&#xff0c;map 数组中存储的都是指针如果 map 数组满了怎么办&#xff1f;很简单&#xff0c;再申请一块更大的连续空间供 map 数组使用&#xff0c;将原有数据&…

每天一道leetcode:433. 最小基因变化(图论中等广度优先遍历)

今日份题目&#xff1a; 基因序列可以表示为一条由 8 个字符组成的字符串&#xff0c;其中每个字符都是 A、C、G 和 T 之一。 假设我们需要调查从基因序列 start 变为 end 所发生的基因变化。一次基因变化就意味着这个基因序列中的一个字符发生了变化。 例如&#xff0c;&quo…

博弈论 | 斐波那契博弈

斐波那契博弈 博弈论是二人或多人在平等的对局中各自利用对方的策略变换自己的对抗策略,达到取胜目标的理论。博弈论是研究互动决策的理论。博弈可以分析自己与对手的利弊关系,从而确立自己在博弈中的优势,因此有不少博弈理论,可以帮助对弈者分析局势,从而采取相应策略,最终达…

计算机提示mfc120u.dll缺失(找不到)怎么解决

在计算机领域&#xff0c;mfc120u.dll是一个重要的动态链接库文件。它包含了Microsoft Foundation Class (MFC) 库的特定版本&#xff0c;用于支持Windows操作系统中的应用程序开发。修复mfc120u.dll可能涉及到解决与该库相关的问题或错误。这可能包括程序崩溃、运行时错误或其…

13.实现业务功能--板块信息

目录 获取在首页中显示的版块 1. 实现逻辑 2. 创建扩展 Mapper.xml 3. 修改 DAO 4. 创建 Service 接口 5. 实现 Service 接口 6. 生成测试方法 7. 实现 Controller 8. 实现前端页面 在数据库中执行以下 SQL 语句&#xff1a; INSERT INTO t_board (id, name, article…

浅析Linux SCSI子系统:调试方法

文章目录 SCSI日志调试功能scsi_logging_level调整SCSI日志等级 SCSI trace events使能SCSI trace events方式一&#xff1a;通过set_event接口方式二&#xff1a;通过enable 跟踪trace信息 相关参考 SCSI日志调试功能 SCSI子系统支持内核选项CONFIG_SCSI_LOGGING配置日志调试…

kafka晋升之路-理论+场景

kafka晋升之路 一&#xff1a;故事背景二&#xff1a;核心概念2.1 系统架构2.2 生产者&#xff08;Producer&#xff09;2.2.1 生产者分区2.2.2 生产者分区策略 2.3 经纪人&#xff08;Broker&#xff09;2.3.1 主题&#xff08;Topic&#xff09;2.3.2 分区&#xff08;Partit…

WPS-RCE

版本&#xff1a; WPS Office 2023 个人版 < 11.1.0.12313 WPS Office 2019 企业版 < 11.8.2.12085 原理&#xff1a; Office 中的 WebExtension&#xff08;通常称为 Office 插件或 Office 应用程序&#xff09;是一种用于扩展 Microsoft Office 功能的技术。Office …

回归预测 | MATLAB实现CSO-SVM布谷鸟优化算法优化支持向量机多输入单输出回归预测(多指标,多图)

回归预测 | MATLAB实现CSO-SVM布谷鸟优化算法优化支持向量机多输入单输出回归预测&#xff08;多指标&#xff0c;多图&#xff09; 目录 回归预测 | MATLAB实现CSO-SVM布谷鸟优化算法优化支持向量机多输入单输出回归预测&#xff08;多指标&#xff0c;多图&#xff09;效果一…

FPGA应用学习笔记-----布线布局优化

优化约束&#xff1a; 设置到最坏情况下会过多 布局和布线之间的关系&#xff1a; 最重要的是与处理器努力的&#xff0c;挂钩允许设计者调整处理器努力的程度 逻辑复制&#xff1a; 不能放置多个负载&#xff0c;只使用在关键路径钟 减少布线延时&#xff0c;但会增加面积&a…

阿里云故障洞察提效 50%,全栈可观测建设有哪些技术要点?

本文根据作者在「TakinTalks 稳定性社区 」公开分享整理而成 #一分钟精华速览# 全栈可观测是一种更全面、更综合和更深入的观测能力&#xff0c;能协助全面了解和监测系统的各个层面和组件&#xff0c;它不仅仅是一个技术上的概念&#xff0c;更多地是技术与业务的结合。在“…

SpringBoot + MyBatis-Plus构建树形结构的几种方式

1. 树形结构 树形结构&#xff0c;是指&#xff1a;数据元素之间的关系像一颗树的数据结构。由树根延伸出多个树杈 它具有以下特点&#xff1a; 每个节点都只有有限个子节点或无子节点&#xff1b;没有父节点的节点称为根节点&#xff1b;每一个非根节点有且只有一个父节点&a…

整理mongodb文档:批量操作

个人博客 整理mongodb文档:批量操作 个人公众号&#xff0c;求关注&#xff0c;文章如有不明&#xff0c;请指出。 文章概叙 本文讲的是关于bulkwrite的用法&#xff0c;依旧是在shell下使用。 关于批量操作 Performs multiple write operations with controls for order …

【Linux命令详解 | wget命令】 wget命令用于从网络下载文件,支持HTTP、HTTPS和FTP协议

文章标题 简介一&#xff0c;参数列表二&#xff0c;使用介绍1. 基本文件下载2. 递归下载整个网站3. 限制下载速率4. 防止SSL证书校验5. 断点续传6. 指定保存目录7. 自定义保存文件名8. 增量下载9. 使用HTTP代理10. 后台下载 总结 简介 在编程世界中&#xff0c;处理网络资源是…

Unity C# 引用池 ReferencePool

Unity C# 引用池 ReferencePool 1.目的 对于多次创建的数据使用new 关键字是十分消耗性能的&#xff0c;使用完成后由GC去自动释放&#xff0c;当一个类型的数据频繁创建可以使用引用池进行管理。 2.实现 项目目录 IReference 接口 要放入引用池的数据只需要继承这个接口…

如何使用CSS实现一个渐变背景效果?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 使用CSS实现渐变背景效果⭐ 线性渐变&#xff08;Linear Gradient&#xff09;⭐ 径向渐变&#xff08;Radial Gradient&#xff09;⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订…

unity 之 Vector 数据类型

文章目录 Vector 1Vector 2Vector 3Vector 4 Vector 1 在Unity中&#xff0c;Vector1 并不是一个常见的向量类型。 如果您需要表示标量&#xff08;单个值&#xff09;或者只需要一维的数据&#xff0c;通常会直接使用浮点数&#xff08;float&#xff09;或整数&#xff08;in…

基于51单片机直流电机转速数码管显示控制系统

一、系统方案 本文主要研究了利用MCS-51系列单片机控制PWM信号从而实现对直流电机转速进行控制的方法。本文中采用了三极管组成了PWM信号的驱动系统&#xff0c;并且对PWM信号的原理、产生方法以及如何通过软件编程对PWM信号占空比进行调节&#xff0c;从而控制其输入信号波形等…

c++如何解决内存泄漏

Linxu Linux系统下解决内存泄漏可以使用valgrind工具。 下载valgrind sudo apt-get install valgrind Linux下使用valgrind g -g -o app test.cpp valgrind --leak-checkfull ./app 代码如下 #include<iostream> using namesapce std; int main() {int i 0;int * …