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

LRU Cache

文章目录

  • 1. 什么是LRU Cache
  • 2. LRU Cache的实现
  • 3. LRU Cache的OJ
    • 题目分析
    • AC代码

1. 什么是LRU Cache

LRU是Least Recently Used的缩写,意思是最近最少使用,它是一种Cache替换算法
什么是Cache?
狭义的Cache指的是位于CPU和主存间的快速RAM, 通常它不像系统主存那样使用DRAM技术,而使用昂贵但较快速的SRAM技术。 广义上的Cache指的是位于速度相差较大的两种硬件之间, 用于协调两者数据传输速度差异的结构。除了CPU与主存之间有Cache, 内存与硬盘之间也有Cache,乃至在硬盘与网络之间也有某种意义上的Cache── 称为Internet临时文件夹或网络内容缓存等。
在这里插入图片描述
而Cache的容量有限,那如果cache满了怎么办?
当Cache的容量用完后,而又有新的内容需要添加进来时, 就需要挑选并舍弃原有的部分内容,从而腾出空间来放新内容。
那应该选取那一部分的内容和新内容进行替换呢?这就涉及到cache的替换算法,而LRU Cache就是cache替换算法中的一种!
LRU Cache 的替换原则就是将最近最少使用的内容替换掉。其实,LRU译成最久未使用会更形象, 因为该算法每次替换掉的就是一段时间内最久没有使用过的内容。

2. LRU Cache的实现

那要实现一个LRU Cache其实并不难,方法和思路有很多;但是想要实现一个高效(所有操作都是O(1) )的LRU Cache是有难度的

实现LRU Cache的方法和思路很多,但是要保持高效实现O(1)的put和get,那么使用双向链表和哈希表的搭配是最高效和经典的。
使用双向链表是因为双向链表可以实现任意位置O(1)的插入和删除,使用哈希表是因为哈希表的增删查改也是O(1)。
在这里插入图片描述

那具体到底应该应该怎么做呢?

下面我们借助LeetCode上的一道OJ来给大家进行一个详细的讲解。

3. LRU Cache的OJ

题目链接: link

我们来看一下题目:
在这里插入图片描述
在这里插入图片描述
大家可以自己先看一下题目,我们看到题目中要求函数 get 和 put 必须以 O(1) 的平均时间复杂度运行。

那我们来分析一下要如何实现

题目分析

题目要求我们实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。
这个 LRUCache 类的要求是这样的:

在这里插入图片描述

那我们要如何实现呢?

其实分析完题目我们很容易能想到用哈希表,那当然C++里面我们就用unordered_map去搞,那这样的话get函数(其实就是查找嘛)就能达到O(1);然后put呢,put也是要查找嘛,找到就更新,找不到就插入。那这样就也是O(1)。
但是呢我们真正还要考虑还有——如果插入操作导致关键字数量超过 capacity ,我们此时就要进行LRU替换,则应该 逐出 最久未使用的关键字。
那我们要如何实现LRU的替换呢?满的话应该逐出谁啊?如何找到最久未被使用的那个呢?

🆗,那上面也提到了,使用双向链表和哈希表的搭配是最高效和经典的:

在这里插入图片描述
我们再搞一个list(list底层就是带头双向循环链表),让它里面也存储数据(相当于数据存储两份),然后我们可以默认认为尾部的那个元素就是最近最少用(最久未被使用)的(按头部实现也可)
然后如果有新插入的元素或者被访问(get一个已有的值)的元素我就把它移到链表头部。
这样我们需要替换的时候,那么链表尾部的那个就是最久未被使用的那个。

但是呢?

在这里插入图片描述
如果我们就这样设计的话,还存在一个问题。
就是更新的时候其实复杂度是O(N)
为什么呢?
更新的情况就是调用put先在哈希表里面查找到key是已存在的,那然后我们要修改,哈希表里面我找到这个就可以直接修改。
但是,在list里面我们是不是也要修改啊,因为我们替换的时候找最久未被使用的那个值就是要从list里面找呢。
但是要修改list的话我们知不知道当前要修改的这个元素是list里面的哪一个元素?
是不知道的,所以还得遍历list去找。找到之后更新一下,然后把它移到头部去,更新顺序。
那这里遍历查找的话不就是O(N)了嘛。

那这个问题我们如何解决一下呢?

如何做到在哈希表里面找到这个key之后,就直接能获取到他在list中的位置,而不需要去遍历查找呢?

那这里有一个非常巧的设计是这样来解决的:

还是list和unordered_map搭配。
list里面呢还是存key-value的键值对pair。然后哈希表里面key还是要存的,但是不再像上面写的那样直接存key对应的数据value,而是存这个key对应的元素在list里面的对应的迭代器。(那这样真正的数据就只存在list里面)
在这里插入图片描述
那这样的话如果更新的话,首先我们在哈希表里面找到key,然后通过它里面存的该元素在list中的迭代器,就可以直接修改list里面存放的数据。
然后再把它放到链表头部(两种方法,后面会提到)。
当然list<pair<int,int>>::iterator这个类型比较长,我们也可以typedef一下
在这里插入图片描述

那下面我们来尝试写一下代码:

AC代码

那首先我们应该还要再增加一个成员变量:

即cache的容量capactity,因为有时候我们还需要判断cache满不满。
在这里插入图片描述

然后我们来逐一实现一下它的3个成员函数:

在这里插入图片描述

首先是它的构造函数LRUCache:

那这个很简单,就是初始化一下capacity就行了。
剩下两个成员变量是自定义类型,有默认构造。
在这里插入图片描述

那然后是get函数:

get呢也很简单,就是通过key判断在不在嘛。
如果在的话,就返回key对应的值;如果不在,返回-1。
那我们就可以直接调用unordered_map的find函数,根据find的返回结果判断,如果找到了,我们要返回什么呢?
在这里插入图片描述
🆗,首先这里的ret是啥啊,这里find返回的是迭代器,找到的话返回的就是key对应的这个元素的迭代器。
那我们要返回这个key对应的那个有效的值,那真正的数据是存在list里面的。
我们通过谁可以找到list里面存储的这个key对应的元素呢?
🆗,现在unordered_map里面的value存的是list里面这个key对应元素的迭代器(有点绕了这里😂)。
在这里插入图片描述
所以ret->second就是list里面这个元素的迭代器,但是我们想要拿到的是这个元素的key对应的值,即ret->second->second,这里要取两次second。
在这里插入图片描述

🆗,那这样就完了嘛?

还没有。
如果get的时候成功找到了这个数据,那相当于访问了它,那cache里面存储数据的顺序是不是就要调整了。是不是要把这个刚被访问的元素放到链表头部啊。

那如何在list里面调整这个顺序呢?我们上面提到有两种方式:

首先第一种方法我们可以先把这个元素删除掉,然后在头插到头部。
但是这样的话记得还要更新一下unordered_map里面存的对应的那个迭代器,因为删完之后这个迭代器就失效了。之前的文章我们模拟实现过list,我们知道list的迭代器其实就是对结点指针的封装嘛,这个结点删除的话它这个结点指针指向的空间就释放了。
所以这种方法好像有点麻烦。
那我就可以考虑用另外一种——直接调用list的splice 接口转移结点。
那splice 这个函数其实我们之前也有提到过
在这里插入图片描述
它可以把一个链表的一部分转移到另一个链表(当然也可以是同一个链表直接进行转移)
所以我们就可以直接调用splice将这个结点转移到list的头部。
在这里插入图片描述

那最后就剩下put:

那put的话呢无非就两种操作
如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。
当然插入的时候需要判断:
如果插入操作导致关键字数量超过 capacity ,则应该 逐出 最久未使用的关键字,然后插入新值。
另外不论是插入还是更新,都应该把插入或更新的值放到链表头部。

那我们来写一下代码:

在这里插入图片描述
🆗,那到这里就完事了。

我们来提交一下:

在这里插入图片描述
没有问题!

完整代码:

class LRUCache {
public:LRUCache(int capacity):_capacity(capacity){}int get(int key) {auto ret=_hashmap.find(key);if(ret!=_hashmap.end()){LtIter it=ret->second;//将it对应的结点转移到链表头部_LRUList.splice(_LRUList.begin(),_LRUList,it);return it->second;}return -1;}void put(int key, int value) {auto ret=_hashmap.find(key);//如果找到,更新值if(ret!=_hashmap.end()){LtIter it=ret->second;it->second=value;//将it对应的结点转移到链表头部_LRUList.splice(_LRUList.begin(),_LRUList,it);}else//找不到,插入{//如果满了需要先删除最久未使用的值if(_capacity==_hashmap.size()){pair<int,int> back=_LRUList.back();_hashmap.erase(back.first);_LRUList.pop_back();}//插入_LRUList.push_front(make_pair(key,value));_hashmap[key]=_LRUList.begin();}}
private:typedef list<pair<int,int>>::iterator LtIter;//hash做到查找更新/插入是O(1)unordered_map<int, LtIter> _hashmap;//LRU 默认链表尾部的是最久未被使用的list<pair<int, int>> _LRUList;size_t _capacity;
};

那我们这篇文章就到这里…

相关文章:

LRU Cache

文章目录 1. 什么是LRU Cache2. LRU Cache的实现3. LRU Cache的OJ题目分析AC代码 1. 什么是LRU Cache LRU是Least Recently Used的缩写&#xff0c;意思是最近最少使用&#xff0c;它是一种Cache替换算法。 什么是Cache&#xff1f; 狭义的Cache指的是位于CPU和主存间的快速RAM…...

软件测试面试题整理

软件测试的几个阶段 在进行Beta测试之前和之后&#xff0c;通常会进行以下几种测试&#xff1a; 内部测试&#xff08;Internal Testing&#xff09; 在Beta测试之前&#xff0c;开发团队会进行内部测试&#xff0c;对软件进行全面的测试。这个阶段包括单元测试、集成测试和系…...

C++三剑客之std::variant(二):深入剖析

目录 1.概述 2.辅助类介绍 2.1.std::negation 2.2.std::conjunction 2.3.std::is_destructible 2.4.std::is_object 2.5.is_default_constructible 2.6.std::is_trivially_destructible 2.7.std::in_place_type和std::in_place_index 3.原理分析 3.1.存储分析 3.2.…...

实验一 安装和使用Oracle数据库

&#x1f57a;作者&#xff1a; 主页 我的专栏C语言从0到1探秘C数据结构从0到1探秘Linux菜鸟刷题集 &#x1f618;欢迎关注&#xff1a;&#x1f44d;点赞&#x1f64c;收藏✍️留言 &#x1f3c7;码字不易&#xff0c;你的&#x1f44d;点赞&#x1f64c;收藏❤️关注对我真的…...

软件工程研究生后期总结

写这篇随笔的时候&#xff0c;我已经处于研究生阶段的后期&#xff0c;只剩下一个硕论答辩即可结束研究生生涯。趁有闲暇时间&#xff0c;我希望可以从实习、兼职、论文和求职等几个角度重新整理一下研究生后期的工作和收获&#xff0c;以及对未来工作和生活做出展望。 首先简…...

Java爬虫爬取图片壁纸

Java爬虫 以sougou图片为例&#xff1a;https://pic.sogou.com/ JDK17、SpringBoot3.2.X、hutool5.8.24实现Java爬虫&#xff0c;爬取页面图片 项目介绍 开发工具&#xff1a;IDEA2023.2.5 JDK&#xff1a;Java17 SpringBoot&#xff1a;3.2.x 通过 SpringBoot 快速构建开发环境…...

红队打靶练习:HOLYNIX: V1

目录 信息收集 1、arp 2、netdiscover 3、nmap 4、nikto whatweb 目录探测 1、gobuster 2、dirsearch 3、dirb 4、feroxbuster WEB sqlmap 1、爆库 2、爆表 3、爆列 4、爆字段 后台登录 1、文件上传 2、文件包含 3、越权漏洞 反弹shell 提权 总结 信息…...

elasticsearch[二]-DSL查询语法:全文检索、精准查询(term/range)、地理坐标查询(矩阵、范围)、复合查询(相关性算法)、布尔查询

ES-DSL查询语法&#xff08;全文检索、精准查询、地理坐标查询&#xff09; 1.DSL查询文档 elasticsearch 的查询依然是基于 JSON 风格的 DSL 来实现的。 1.1.DSL 查询分类 Elasticsearch 提供了基于 JSON 的 DSL&#xff08;Domain Specific Language&#xff09;来定义查…...

Microsoft Word 设置底纹

Microsoft Word 设置底纹 References 打开文档页面&#xff0c;选中特定段落或全部文档 在“段落”中单击“边框”下三角按钮 在列表中选择“边框和底纹”选项 在“边框和底纹”对话框中单击“底纹”选项卡 在图案样式和图案颜色列表中设置合适颜色的底纹&#xff0c;单击“确…...

【大数据】Flink 详解(九):SQL 篇 Ⅱ

《Flink 详解》系列&#xff08;已完结&#xff09;&#xff0c;共包含以下 10 10 10 篇文章&#xff1a; 【大数据】Flink 详解&#xff08;一&#xff09;&#xff1a;基础篇【大数据】Flink 详解&#xff08;二&#xff09;&#xff1a;核心篇 Ⅰ【大数据】Flink 详解&…...

workflow源码解析:GoTask

关于go task 提供了另一种更简单的使用计算任务的方法&#xff0c;模仿go语言实现的go task。 使用go task来实计算任务无需定义输入与输出&#xff0c;所有数据通过函数参数传递。 与ThreadTask 区别 ThreadTask 是有模板&#xff0c;IN 和 OUT&#xff0c; ThreadTask 依赖…...

SpringMVC入门案例

引言 Spring MVC是一个基于MVC架构的Web框架&#xff0c;它的主要作用是帮助开发者构建Web应用程序。它提供了一个强大的模型驱动的开发方式&#xff0c;可以帮助开发者实现Web应用程序的各种功能&#xff0c;如请求处理、数据绑定、视图渲染、异常处理等。 开发步骤 1.创建we…...

Docker本地私有仓库搭建配置指导

一、说明 因内网主机需要拉取镜像进行Docker应用&#xff0c;因此需要一台带外主机作为内网私有仓库来提供内外其他docker业务主机使用。参考架构如下&#xff1a; 相关资源&#xff1a;加密、Distribution registry、Create and Configure Docker Registry、Registry部署、D…...

python 通过定时任务执行pytest case

这段Python代码使用了schedule库来安排一个任务&#xff0c;在每天的22:50时运行。这个任务执行一个命令来运行pytest&#xff0c;并生成一个报告。 代码开始时将job_done变量设为False&#xff0c;然后运行预定的任务。一旦任务完成&#xff0c;将job_done设置为True并跳出循…...

算法面试题:合并两个有序链表

描述&#xff1a;给定两个按非递减顺序排列的链表&#xff0c;合并两个链表&#xff0c;并将结果按非递减顺序排列。 例如&#xff1a; # 链表 1: 1 -> 2 -> 4 # 链表 2: 1 -> 3 -> 4合并后的链表应该是&#xff1a;1 -> 1 -> 2 -> 3 -> 4 -> 4 …...

LaWGPT安装和使用教程的复现版本【细节满满】

文章目录 前言一、下载和部署1.1 下载1.2 环境安装1.3 模型推理 总结 前言 LaWGPT 是一系列基于中文法律知识的开源大语言模型。该系列模型在通用中文基座模型&#xff08;如 Chinese-LLaMA、ChatGLM等&#xff09;的基础上扩充法律领域专有词表、大规模中文法律语料预训练&am…...

西门子博途用SCL语言写的入栈出栈

1.用户登录 #pragma code ("useadmin.dll") #include "PWRT_api.h" #pragma code() PWRTLogin(1) 2.用户退出 #pragma code ("useadmin.dll") #include "PWRT_api.h" #pragma code() PWRTLogout(); 3.画面跳转 SetPictureName("P…...

密码产品推介 | 沃通安全电子签章系统(ES-1)

产品介绍 沃通安全电子签章系统&#xff08;ES-1&#xff09;是一款基于密码技术、完全自主研发的商用密码产品&#xff0c;严格遵循国家密码管理局制定的相关标准&#xff0c;可为企业和个人提供安全、合规的电子签章功能服务。产品的主要用途是为各类文书、合同、表单等电子…...

蓝桥杯真题(Python)每日练Day1

说明&#xff1a;在CSP认证的基础上&#xff08;可以看看本人CSP打卡系列的博客&#xff09;备赛2024蓝桥杯&#xff08;Python&#xff09;&#xff0c;本人专业&#xff1a;大数据与数据科学 因此对python要求熟练掌握&#xff0c;通过练习蓝桥杯既能熟悉语法又能锻炼算法和思…...

IDEA怎么用Devtools热部署

IDEA怎么用Devtools热部署 大家知道在项目开发过程中&#xff0c;有时候会改动代码逻辑或者修改数据结构&#xff0c;为了能使改动的代码生效&#xff0c;往往需要重启应用查看改变效果&#xff0c;这样会相当耗费时间。 重启应用其实就是重新编译生成新的Class文件&#xff0…...

boost.circular_buffer的使用和介绍

C 文章目录 C 很多时候&#xff0c;我们需要在内存中记录最近一段时间的数据&#xff0c;如操作记录等。由于这部分数据记录在内存中&#xff0c;因此并不能无限递增&#xff0c;一般有容量限制&#xff0c;超过后就将最开始的数据移除掉。在stl中并没有这样的数据结构&#xf…...

深入理解Java中的ThreadLocal

第1章&#xff1a;引言 大家好&#xff0c;我是小黑。今天咱们来聊聊ThreadLocal。首先&#xff0c;让咱们先搞清楚&#xff0c;ThreadLocal是个什么玩意儿。简单说&#xff0c;ThreadLocal可以让咱们在每个线程中创建一个变量的“私有副本”。这就意味着&#xff0c;每个线程…...

【重点】【DP】300. 最长递增子序列

题目 更好的方法是耐心排序&#xff0c;参见《算法小抄》的内容&#xff01;&#xff01;&#xff01; 法1&#xff1a;DP 基础解法必须掌握&#xff01;&#xff01;&#xff01; class Solution {public int lengthOfLIS(int[] nums) {if (nums null || nums.length 0) …...

使用freessl为网站获取https证书及配置详细步骤

文章目录 一、进入freessl网站二、修改域名解析记录三、创建证书四、配置证书五、服务启动 一、进入freessl网站 首先进入freessl网站&#xff0c;需要注册一个账号 freessl网站 进入网站后填写自己的域名 接下来要求进行DCV配置 二、修改域名解析记录 到域名管理处编辑域名…...

Java-初识正则表达式 以及 练习

目录 什么是正则表达式&#xff1f; 1. 正则表达式---字符类&#xff08;一个大括号匹配一个字符&#xff09;&#xff1a; 2. 正则表达式---预字符类&#xff08;也是匹配一个字符&#xff09;&#xff1a; 正则表达式---数量词 &#xff08;可以匹配多个字符&#xff09;…...

【Flutter 问题系列第 80 篇】TextField 输入框组件限制可输入的最大长度后,输入的内容中包含表情符号时,获取输入的内容数还是会超出限制的问题

这是【Flutter 问题系列第 80 篇】&#xff0c;如果觉得有用的话&#xff0c;欢迎关注专栏。 博文当前所用 Flutter SDK&#xff1a;3.10.5、Dart SDK&#xff1a;3.0.5 一&#xff1a;问题描述 在输入用户名称、简介等内容时&#xff0c;一般我们都会限制输入框内最大可输入…...

漏洞检测和评估【网站子域扫描工具02】

上一篇&#xff1a;爬取目标网站的域名和子域名【网站子域扫描工具01】 在Python中&#xff0c;有一些流行的漏洞扫描库可以对子域进行漏洞扫描和评估&#xff0c;比如Nmap、Sublist3r等。 1.端口扫描 以下是一个简单的示例代码&#xff0c;展示了如何使用Nmap进行基本的端口扫…...

压力测试+接口测试(工具jmeter)

jmeter是apache公司基于java开发的一款开源压力测试工具&#xff0c;体积小&#xff0c;功能全&#xff0c;使用方便&#xff0c;是一个比较轻量级的测试工具&#xff0c;使用起来非常简单。因 为jmeter是java开发的&#xff0c;所以运行的时候必须先要安装jdk才可以。jmeter是…...

LeetCode 46 全排列

题目描述 全排列 给定一个不含重复数字的数组 nums &#xff0c;返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。 示例 1&#xff1a; 输入&#xff1a;nums [1,2,3] 输出&#xff1a;[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]示例 2&#xff1a; 输入…...

npm install 无反应 npm run serve 无反应

说明情况&#xff1a;其实最开始我就是发现我跟着黑马的苍穹外卖的前端day2的环境搭建做的时候&#xff0c;到这一步出现了问题&#xff0c;无论我怎么 npm install 和 npm run serve 都没有像黑马一样有很多东西进行加载&#xff0c;因此我换了一种方法 1.在这个文件夹下cmd …...