算法 —— LRU算法

算法 —— LRU算法

  • LRU
      • LRU算法的工作原理:
      • 实现方法:
      • 性能考虑:
  • 模拟过程
    • splice函数
      • 对于`std::list`和`std::forward_list`
        • 基本语法:
        • 功能描述:
      • 示例:
      • 注意事项:

如果大家已经学习过了Cache的替换算法和页面置换算法,大家一定对LRU(Least Recently Used,最近最少使用)不陌生,我们今天来研究下这个算法:

https://leetcode.cn/problems/lru-cache/description/

在这里插入图片描述这里有一个例子:
在这里插入图片描述

LRU

LRU(Least Recently Used,最近最少使用)算法是一种常用的缓存淘汰策略,用于在缓存空间有限的情况下决定哪些数据应该被保留,哪些数据应该被移除LRU算法的基本理念是:如果某数据在最近一段时间内没有被访问,那么在未来被访问的可能性也比较低。反之,如果某数据被频繁访问,那么它应当被保留在缓存中

LRU算法的工作原理:

  1. 缓存初始化:当缓存初始化时,它是空的。
  1. 数据访问
  • 如果请求的数据已经在缓存中,称为缓存命中(Hit),则更新该数据项的访问状态,表明它最近被使用过。
  • 如果请求的数据不在缓存中,称为缓存未命中(Miss),则需要从主存或其他存储中加载数据到缓存。
  1. 数据淘汰
  • 当缓存已满,而新的数据需要加入缓存时,LRU算法会选择最近最少使用的数据项进行淘汰,以便为新数据腾出空间。
  • “最近最少使用”的定义是:在当前时刻,从上次访问到现在时间间隔最长的数据。

实现方法:

LRU算法可以通过多种数据结构来实现,其中最常见的是使用双向链表和哈希表的组合:

  • 双向链表:用于维护数据项的访问顺序,最新访问的数据放在链表头部,最久未访问的数据放在链表尾部。
  • 哈希表:用于快速查找数据项在双向链表中的位置。

当数据被访问时,它从链表中的当前位置移动到链表头部。当缓存满时,链表尾部的数据项被移除。

性能考虑:

LRU算法虽然直观且有效,但在某些情况下可能会有性能开销,尤其是当数据集非常大时,维护链表的插入和删除操作可能会成为瓶颈。此外,如果数据访问模式中存在大量突发性的随机访问,LRU算法可能无法很好地预测哪些数据是真正需要保留在缓存中的。

尽管如此,LRU仍然是许多缓存系统中首选的淘汰策略,因为它在大多数情况下能够提供较好的命中率和性能。在软件和硬件缓存管理中,LRU算法都有广泛应用。例如,在Web服务器缓存、数据库查询缓存、CPU缓存和虚拟内存管理系统中都能见到它的身影。

模拟过程

我们这里用unordered_map和list来模拟:

#pragma once
#include<iostream>
#include<unordered_map>
#include<list>
using namespace std;class LRUCache {
public:LRUCache(int capacity) {}int get(int key) {}void put(int key, int value) {}private:size_t _capacity; //容量//查询unordered_map<int ,list<pair<int,int>>::iterator> _LRUMap;//插入删除list<pair<int, int>> _LRUList;
};

unordered_map可以帮助我们查询是O(1)的时间复杂度,list帮助我们模拟过程,这里我们unordered_map的第二个键值对是list的迭代器,这个方便我们直接修改顺序是O(1)的操作:

我们来看看:
在这里插入图片描述我们按照这个过程来模拟:

LRUCache lRUCache = new LRUCache(2);
lRUCache.put(1, 1); // 缓存是 {1=1}
lRUCache.put(2, 2); // 缓存是 {1=1, 2=2}
lRUCache.get(1);    // 返回 1
lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}
lRUCache.get(2);    // 返回 -1 (未找到)
lRUCache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3}
lRUCache.get(1);    // 返回 -1 (未找到)
lRUCache.get(3);    // 返回 3
lRUCache.get(4);    // 返回 4

在这里插入图片描述
在这里插入图片描述
这个时候执行了查询操作:

lRUCache.get(1);    // 返回 1

在这里插入图片描述接下来我们放入了3,3:

lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}

在这里插入图片描述
以此类推,我们可以得出代码:

#pragma once
#include <iostream>
#include <unordered_map>
#include <list>
using namespace std;class LRUCache {
public:// 构造函数,初始化缓存容量LRUCache(int capacity) : _capacity(capacity) {}// 获取缓存中的值,如果存在则更新其位置至最近使用int get(int key) {// 查找键值对应的迭代器auto ret = _LRUMap.find(key);if (ret != _LRUMap.end()) { // 如果找到了键值list<pair<int, int>>::iterator it = ret->second;// 将找到的元素移动到列表的前端,表示最近被使用_LRUList.splice(_LRUList.begin(), _LRUList, it);// 返回值return it->second;} else {// 如果没有找到,返回-1return -1;}}// 插入或更新键值对void put(int key, int value) {// 查找键值对应的迭代器auto ret = _LRUMap.find(key);if (ret == _LRUMap.end()) { // 如果没找到,即键值不存在// 如果缓存已满if (_capacity == _LRUList.size()) {// 删除最旧的元素(列表的最后一个元素)_LRUMap.erase(_LRUList.back().first);_LRUList.pop_back();}// 插入新的键值对到列表前端_LRUList.push_front(make_pair(key, value));// 更新或添加键值对应的迭代器到哈希表_LRUMap[key] = _LRUList.begin();} else {// 如果键值已存在,更新值并移动到列表前端list<pair<int, int>>::iterator it = ret->second;it->second = value; // 更新值// 将元素移动到列表前端_LRUList.splice(_LRUList.begin(), _LRUList, it);}}// 打印缓存内容void Print() {for (auto e : _LRUList) {cout << "key值: " << e.first << " value值: "<< e.second << endl;}cout << endl;}private:size_t _capacity; // 缓存的最大容量// 用于快速查找键值对应的迭代器unordered_map<int, list<pair<int, int>>::iterator> _LRUMap;// 存储键值对的有序列表,用于维护最近使用的顺序list<pair<int, int>> _LRUList;
};

在这里插入图片描述在这里插入图片描述

splice函数

splice是C++标准模板库(STL)中容器(如std::list, std::forward_list, std::deque)的一个成员函数,用于在容器之间或容器内部移动元素。splice函数允许你将一个容器中的元素或一组连续的元素无缝地插入到另一个相同类型的容器的指定位置,而无需复制或构造元素。这对于需要高效地重新组织元素顺序的情况非常有用。

对于std::liststd::forward_list

对于std::liststd::forward_listsplice的用法如下:

基本语法:
void splice(position, x);
void splice(position, x, iterator i);
void splice(position, x, iterator first, iterator last);
  • position:在当前容器中插入元素的位置,对于std::list,可以是iteratorconst_reference;对于std::forward_list,总是before_begin()
  • x:源容器,必须与当前容器具有相同的类型。
  • i:源容器中的单个元素迭代器。
  • first, last:源容器中元素范围的迭代器。
功能描述:
  • splice(position, x);:将x容器中的所有元素移动到当前容器的position位置之前。
  • splice(position, x, i);:将x容器中由i指向的单个元素移动到当前容器的position位置之前。
  • splice(position, x, first, last);:将x容器中由[first, last)区间定义的元素序列移动到当前容器的position位置之前。

示例:

假设我们有两个std::list<int>容器,list1list2,我们想把list2中的元素5移动到list1的开始位置:

std::list<int> list1{1, 2, 3, 4};
std::list<int> list2{5, 6, 7, 8};auto it = list2.find(5);
list1.splice(list1.begin(), list2, it);

现在list1看起来应该是{5, 1, 2, 3, 4},而list2应该是{6, 7, 8}

注意事项:

  • 移动操作是常数时间复杂度的,因此splice非常高效。
  • 被移动的元素将从源容器中移除。
  • 如果两个容器共享同一个分配器(例如,它们是同一个容器的不同部分),splice操作不会抛出异常。

对于std::dequesplice的用法与上述略有不同,因为std::deque不允许在中间插入或删除元素,只能在两端进行。因此,std::dequesplice只接受before_begin()end()作为插入位置,而且只能从另一个std::deque中移动元素到当前std::deque的开头或结尾。

总之,splice是一个强大的工具,可以高效地重新组织容器中的元素,特别是在需要移动大量元素或避免不必要的元素复制时。

具体更多的可以查看官网:

https://legacy.cplusplus.com/

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

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

相关文章

Linux——Shell脚本和Nginx反向代理服务器

1. Linux中的shell脚本【了解】 1.1 什么是shell Shell是一个用C语言编写的程序&#xff0c;它是用户使用Linux的桥梁 Shell 既是一种命令语言&#xff0c;有是一种程序设计语言 Shell是指一种应用程序&#xff0c;这个应用程序提供了一个界面&#xff0c;用户通过这个界面访问…

开放式耳机2024哪家品牌比较好?2024年爆火开放式耳机推荐

很多小伙伴在后台私信我&#xff0c;滴滴我说&#xff0c;最近开放式耳机这么火&#xff0c;他也想要入手一台问问我&#xff0c;有哪些开放式耳机值得现在入手的&#xff0c;作为一个尽职尽业的数码博主&#xff0c;我本来是一个个回复的&#xff0c;但是私信没想到这么多&…

[C++初阶]list的模拟实现

一、对于list的源码的部分分析 1.分析构造函数 首先&#xff0c;我们一开始最先看到的就是这个结点的结构体&#xff0c;在这里我们可以注意到这是一个双向链表。有一个前驱指针&#xff0c;一个后继指针。然后在有一个存储数据的空间 其次它的迭代器是一个自定义类型&#x…

pyinstall 打包基于PyQt5和PaddleOCR的项目为.exe

简介&#xff1a; 最近做了一个小项目&#xff0c;是基于PyQt5和PaddleOCR的。需要将其打包为.exe&#xff0c;然后打包过程中遇到了很多问题&#xff0c;也看了很多教程&#xff0c;方法千奇百怪的&#xff0c;最后也是一步一步给试出来了。记录一下&#xff0c;防止以后忘记…

CSS基础学习之元素定位(6)

目录 1、定位类型 2、取值 2.1、static 2.2、relative 2.3、absolute 2.4、fixed 2.5、stickty 3、示例 3.1、相对定位(relative) 3.2、绝对定位&#xff08;absolute&#xff09; 3.3、固定定位&#xff08;fixed&#xff09; 3.4、粘性定位&#xff08;sticky&…

智慧互联新时代,Vatee万腾平台引领行业变革

在科技日新月异的今天&#xff0c;我们正步入一个前所未有的智慧互联新时代。这个时代&#xff0c;信息如潮水般涌来&#xff0c;数据成为新的石油&#xff0c;驱动着各行各业发生深刻变革。在这场变革的浪潮中&#xff0c;Vatee万腾平台以其卓越的智慧互联技术和前瞻性的战略布…

vue3前端开发-执行npm run dev提示报错怎么解决

vue3前端开发-执行npm run dev提示报错怎么解决&#xff01;今天在本地安装初始化了一个vue3的案例demo。但是当我执行npm run dev想启动它时报错了说&#xff0c;找不到dev。让我检查package.json文件是否包含dev。如下图所示&#xff1a; 实际上&#xff0c;不必惊慌&#xf…

2024全球和国内最常用的弱密码,有没有你的?

密码管理器NordPass分析了来自公开来源的超过4.3TB 的密码数据&#xff0c;找出了当前为止&#xff08;2024年&#xff09;最常用&#xff08;最脆弱&#xff09;的密码。 这些密码主要有下面这些特征&#xff1a; 简单且常用&#xff0c;万年弱密码&#xff0c;比如123456、a…

获利能力段部分特征值不更新,需要手动点派生才更新的问题

一、问题描述&#xff1a;销售订单修改某些特征值字段&#xff0c;保存后&#xff0c;获利能力段对应的字段值没更新。 比如&#xff1a;把销售订单销售组从Z09修改为Z04&#xff0c;保存后&#xff0c;获利能力段重的销售组还是旧值Z09。 1、修改销售组为Z04,然后保存 2、销售…

mac拆分pdf mac如何拆分pdf成多个文件

在数字化办公日益普及的今天&#xff0c;pdf文件因其良好的兼容性和便捷性&#xff0c;已经成为工作和学习中的重要文件格式。然而&#xff0c;有时候我们可能会遇到需要将一个大的pdf文件拆分成多个小文件的情况&#xff0c;以便于管一理和分享。本文将为您详细介绍两种简单易…

【BUG】已解决:java.lang.reflect.InvocationTargetException

已解决&#xff1a;java.lang.reflect.InvocationTargetException 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来到我的主页&#xff0c;我是博主英杰&#xff0c;211科班出身&#xff0c;就职于医疗科技公司&#xff0c;热衷分享知识&#xff0c;武汉城市开发…

[word] word如何编写公式? #微信#知识分享

word如何编写公式&#xff1f; word如何编写公式&#xff1f;Word中数学公式是经常会使用到的&#xff0c;若是要在文档中录入一些复杂的公式&#xff0c;要怎么做呢&#xff1f;接下来小编就来给大家讲一讲具体操作&#xff0c;一起看过来吧&#xff01; 方法一&#xff1a;…

【机器学习】--过采样原理及代码详解

过采样&#xff08;Oversampling&#xff09;是一个在多个领域都有应用的技术&#xff0c;其具体含义和应用方法会根据领域的不同而有所差异。以下是对过采样技术的详细解析&#xff0c;主要从机器学习和信号处理两个领域进行阐述。 一、机器学习中的过采样 在机器学习中&…

完美的用户体验:如何设计一个直观和有效的网站导航?

APP的顶部导航栏对我们来说很熟悉。导航栏是UI设计中不可或缺的一部分&#xff0c;几乎每个页面都使用导航栏。虽然导航栏看起来很简单&#xff0c;不需要太多精力&#xff0c;但是设计一个与产品需求和客户目标高度匹配的导航栏并不是那么容易的。导航栏的设计标准有很多细节需…

JavaWeb服务器-Tomcat(Tomcat概述、Tomcat的下载、安装与卸载、启动与关闭、常见的问题)

Tomcat概述 Tomcat服务器软件是一个免费的开源的web应用服务器。是Apache软件基金会的一个核心项目。由Apache&#xff0c;Sun和其他一些公司及个人共同开发而成。 由于Tomcat只支持Servlet/JSP少量JavaEE规范&#xff0c;所以是一个开源免费的轻量级Web服务器。 JavaEE规范&…

JavaScript 中怎么看数据返回值

文章目录 前言console.log()1. 输出简单的文本2. 输出变量3. 输出表达式的结果4. 输出对象和数组5. 输出多个参数6. 使用模板字符串7. 输出错误信息 alert()基本用法使用场景注意事项 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 我只知道后端程序跑…

React学习笔记02-----React基本使用

一、React简介 想实现页面的局部刷新&#xff0c;而不是整个网页的刷新。AJAXDOM可以实现局部刷新 1.特点 &#xff08;1&#xff09;虚拟DOM 开发者通过React来操作原生DOM&#xff0c;从而构建页面。 React通过虚拟DOM来实现&#xff0c;可以解决DOM的兼容性问题&#x…

跳动的爱 - 动态全屏爱心【前端版本】

要使用HTML、CSS和JavaScript绘制一个全屏且较大的爱心&#xff0c;并且让它有动态效果&#xff0c;可以通过以下步骤实现&#xff1a; HTML: 定义基本的页面结构。 CSS: 定义爱心的样式和动画效果。 JavaScript: 动态调整爱心的位置和大小&#xff0c;使其在页面上移动。 下面…

软考2024下半年考试时间是多少?哪个科目容易考?

软考2024下半年考试时间为 11月9日-12日 2024下半年软考共安排了12个资格的考试&#xff0c;具体为软考高级&#xff1a;系统分析师、系统架构设计师、网络规划设计师、系统规划与管理师&#xff1b;软考中级&#xff1a;软件设计师、网络工程师、信息安全工程师、信息系统监…

【C语言】联合体(union)

文章目录 1.联合体的含义2. 联合体的声明3. 联合体大小的计算4. 联合体的特点 1.联合体的含义 联合体也叫做共用体&#xff0c;是指联合体的所有成员共用同一块内存空间。这也就说明了&#xff0c;联合体的大小至少是其成员所占空间的最大值。 2. 联合体的声明 #include<…