算法 —— LRU算法

news2024/9/21 0:43:19

算法 —— 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 {
            // 如果没有找到,返回-1
            return -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://www.coloradmin.cn/o/1933406.html

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

相关文章

《人性的弱点》

This book is called ‘How to Win Friends & Influence People’. [COPY] 卡耐基《人性的弱点》有什么干货么&#xff1f;

自学鸿蒙HarmonyOS的ArkTS语言<十>@BuilderParam装饰器

作用&#xff1a;当子组件多处使用时&#xff0c;给某处的子组件添加特定功能 一、初始化 1、只能被Builder装饰的方法初始化 2、使用所属自定义组件的builder方法初始化 3、使用父组件的builder方法初始化 - 把父组件的builder传过去&#xff0c;参数名和子组件的builderPar…

【信号频率估计】MVDR算法及MATLAB仿真

目录 一、MVDR算法1.1 简介1.2 原理1.3 特点1.3.1 优点1.3.2 缺点 二、算法应用实例2.1 信号的频率估计2.2 MATLAB仿真代码 三、参考文献 一、MVDR算法 1.1 简介 最小方差无失真响应&#xff08;Mininum Variance Distortionless Response&#xff0c;MVDR&#xff09;算法最…

AI初学者的利器——香橙派AIpro

目录 引言香橙派介绍公司简介&#xff08;来自官网&#xff09;香橙派AIpro介绍香橙派AIPro硬件规格参数开发板接口详情系统登陆与使用指示灯 AI运行实例AI CPU和control CPU的设置方法香橙派AIpro cpu知识查询AIcpu占用率与cpu类别设置 Juypter lab使用JuypterLab介绍JuypterL…

8款可以替代Axure的设计软件推荐

一个好的原型设计工具对于产品经理或者UI/UX设计师来说非常重要。一个好的原型设计软件可以帮助你快速构建一个还原度高、信息结构清晰的原型图&#xff0c;也可以大大降低工作中与同事的沟通成本&#xff0c;更高效地推进工作。 那么&#xff0c;什么是易于使用和免费的原型设…

C51语言及通用I/O口应用

4.1 C51的程序结构 4.2 C51的数据结构 4.3 C51与汇编的混合编程 4.4 C51仿真开发方法 4.5 通用I/O口的简单应用 4.6 通用I/O口的进阶应用 4.1.1 C51语言概述 C51语言是51单片机的一种高级编程语言&#xff0c;与低级语言的汇编语言相比&#xff0c;一方面具有结构化语…

Chapter12 屏幕后处理效果——Shader入门精要学习笔记

Chapter12 屏幕后处理效果 一、屏幕后处理概述以及基本脚本系统1.OnRenderImage 函数 —— 获取屏幕图像2.Graphics.Blit 函数 —— 使用特定的Shader处理3.在Unity中实现屏幕后处理的基本流程4.屏幕后处理基类 二、调整亮度、饱和度和对比度1.BrightnessSaturationAndContrast…

Postman安装使用教程(详解)

目录 一、Postman是什么 二、安装系统要求 三、下载Postman 四、注册和登录Postman 五、创建工作空间 六、创建请求 一、Postman是什么 在安装之前&#xff0c;让我们先来简单了解一下Postman。Postman是一个流行的API开发工具&#xff0c;它提供了友好的用户界面用于发送…

简单实用的企业舆情安全解决方案

前言&#xff1a;企业舆情安全重要吗&#xff1f;其实很重要&#xff0c;尤其面对负面新闻&#xff0c;主动处理和应对&#xff0c;可以掌握主动权&#xff0c;避免股价下跌等&#xff0c;那么如何做使用简单实用的企业舆情解决方案呢&#xff1f; 背景 好了&#xff0c;提取词…

python CMD命令行传参实现:argparse、click、fire

1、argparse 设置传入和默认参数&#xff0c;也可以通过–help参考具体设置参数 bool值 参考&#xff1a; https://docs.python.org/zh-cn/3/howto/argparse.html https://www.bilibili.com/video/BV1nb41157Zc expected one argumrnt 报错&#xff0c;传入坐标类型字符串…

MYSQL 四、mysql进阶 9(数据库的设计规范)

一、为什么需要数据库设计 二、范 式 2.1 范式简介 在关系型数据库中&#xff0c;关于数据表设计的基本原则、规则就称为范式。 可以理解为&#xff0c;一张数据表的设计结 构需要满足的某种设计标准的 级别 。要想设计一个结构合理的关系型数据库&#xff0c;必须满足一定的…

couldn‘t read native报错!Typora中使用Pandoc导出Word失败的解决方法

couldn‘t read native报错&#xff01;Typora中使用Pandoc导出Word失败的解决方法 一、问题描述 在Typora中使用Pandoc将markdown文件导出为word文件时&#xff0c;发生如下图所示错误: 在网上找了资料以后&#xff0c;发现是因为md文件里面有表格&#xff0c;如果把表格删掉…

【深度学习】PyTorch框架(4):初始网络、残差网络 和密集连接网络

1、引言 在本篇文章中&#xff0c;我们将深入探讨并实现一些现代卷积神经网络&#xff08;CNN&#xff09;架构的变体。近年来&#xff0c;学界提出了众多新颖的网络架构。其中一些最具影响力&#xff0c;并且至今仍然具有重要地位的架构包括&#xff1a;GoogleNet/Inception架…

linux搭建mysql主从复制(一主一从)

目录 0、环境部署 1、主服务器配置 1.1 修改mysql配置文件 1.2 重启mysql 1.3 为从服务器授权 1.4 查看二进制日志坐标 2、从服务器配置 2.1 修改mysql配置文件 2.2 重启mysql 2.3 配置主从同步 2.4 开启主从复制 3、验证主从复制 3.1 主服务器上创建test…

Stable Diffusion【美女写实模型】:亚洲女性写实大模型,皮肤细腻光滑!

今天介绍一款专注于亚洲女性写实的SDXL模型&#xff1a;XXMix_9realisticSDXL。该模型绘图质量相当出色&#xff1a;面部在真实感基础上增加了一些轻度的美颜效果&#xff1b;以及增强的光影特效方面效果&#xff1b;只需要简单提示语就可以画出典型的亚洲女孩风格高质量图像。…

通过vue3 + TypeScript + uniapp + uni-ui 实现下拉刷新和加载更多的功能

效果图: 核心代码: <script lang="ts" setup>import { ref, reactive } from vue;import api from @/request/api.jsimport empty from @/component/empty.vueimport { onLoad,onShow, onPullDownRefresh, onReachBottom } from @dcloudio/uni-applet form …

Gradio技术入门(一)

Gradio是一个开源的Python库&#xff0c;旨在让创建机器学习模型的应用界面变得简单快捷。 官网&#xff1a;格罗特 (gradio.app) 一、基本概述 1&#xff0c;技术概述 1. 定义与用途 Gradio通过Python生成一套HTML页面&#xff0c;其中编写好了大部分的组件&#xff0c;主…

《大数据基础》相关知识点及考点,例题

1.6大数据计算模式 1、MapReduce可以并行执行大规模数据处理任务&#xff0c;用于大规模数据集&#xff08;大于1TB&#xff09;的并行运算。MapReduce 极大地方便了分布式编程工作&#xff0c;它将复杂的、运行于大规模集群上的并行计算过程高度地抽象为两个函数一一Map和Redu…

数据库系统概论:数据库完整性

引言 数据库是现代信息系统的心脏&#xff0c;数据的准确性和一致性对于业务流程至关重要。数据库完整性是确保数据质量的基石&#xff0c;它涵盖了数据的正确性、相容性和一致性&#xff0c;是数据安全与业务连续性的保障。 数据库完整性是指数据的精确性、可靠性和逻辑一致…

Gitee使用教程2-克隆仓库(下载项目)并推送更新项目

一、下载 Gitee 仓库 1、点击克隆-复制代码 2、打开Git Bash 并输入复制的代码 下载好后&#xff0c;找不到文件在哪的可以输入 pwd 找到仓库路径 二、推送更新 Gitee 项目 1、打开 Git Bash 用 cd 命令进入你的仓库&#xff08;我的仓库名为book&#xff09; 2、添加文件到 …