数据结构---LRU CACHE

news2024/9/29 3:19:06

什么是LRU

通过之前的学习我们知道计算机在处理任务的时候是先将数据从硬盘中提取出来加载进内存,然后再将内存中的数据加载进入cpu进行计算,但是这里存在一个问题cpu的计算速度非常快,而内存中加载数据的速度又很慢,所以为了提供整机的工作效率我们就得在内存和cpu计算机中添加一个东西叫做缓存,狭义的Cache指的是位于CPU和主存间的快速RAM(缓存),通常它不像系统主存那样使用DRAM技术,而使用昂贵但较快速的SRAM技术。 广义上的Cache指的是位于速度相差较大的两种硬件之间,用于协调两者数据传输速度差异的结构。除了CPU与主存之间有Cache,内存与硬盘之间也有Cache,乃至在硬盘与网络之间也有某种意义上的Cache── 称为Internet临时文件夹或网络内容缓存等。但是Cache的容量有限,因此当Cache的容量用完后,而又有新的内容需要添加进来时, 就需要挑选并舍弃原有的部分内容,从而腾出空间来放新内容。那么将哪部分数据删除这就由LRU来决定,设计一个LRU并不难,但是要想设计一个高效的LRU是很有难度的,这里的高效指的是任何操作的时间复杂度都是o(1), LRU Cache 的替换原则就是将最近最少使用的内容替换掉。其实,LRU译成最久未使用会更形象,因为该算法每次替换掉的就是一段时间内最久没有使用过的内容,那么接下来我们就通过一道题来带着大家模拟实现LRU,题目的内容如下:
在这里插入图片描述
题目给的代码如下:

class LRUCache {
public:
    LRUCache(int capacity) {

    }
    
    int get(int key) {

    }
    
    void put(int key, int value) {

    }
};

/**
 * 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);
 */

模拟实现LRU

通过上面的介绍我们知道get函数就是到缓存里面查找数据,如果找到了就返回数据的内容如果没有找到就返回-1,然后put函数有两个功能一个是往容器里面插入数据,另外一个就是更新容器里面的数据,那么要想实现上述功能的话我们可以添加一个哈希表来实现,这样我们查找数据的时间复杂度是o(1),删除数据的时间复杂度也是o(1),更新数据的时间复杂度也是o(1),但是这么做存在一个问题这里的时间复杂度确实符合要求了,但是我们这里要实现的是LRU啊,在删除数据的时候你怎么知道谁的优先级高谁的优先级低呢?简单的说就是你怎么知道要删除的是哪些数据呢?所以单独的一个哈希表肯定是无法满足要求的,那么这时有人说那能不能添加一个优先级队列来辅助实现呢?答案也是不行的,优先级队列确实可以将数据的优先级进行排序,我们一下子就可以知道谁的优先级最高,谁的优先级最低,但是这里有个问题当我们更新数据的时候将优先级调制最高,那你如何将里面的数据优先级队列中的那个元素进行调整呢?即使能够调整那能不能保证时间复杂度是o(1)呢?所以这种方法肯定是不行的那么这个时候有小伙伴又会说能不能添加引用计数的形式来实现呢?答案是可以的但是这么做还是太麻烦了,我们有一个很简单的方法就是添加链表来进行辅助,那么这里的代码如下:

class LRUCache {
public:
    LRUCache(int capacity) {

    }
    
    int get(int key) {

    }
    
    void put(int key, int value) {

    }
private:
    unordered_map<int, int> _hashmap;
    list<pair<int,int>> _l1;
};

查找和新增可以通过哈希表来做到时间复杂度为O(1),在删除的时候可以根据链表中元素的位置来判断优先级的高低,如果对一个元素进行更新的话就将这个链表调整值头部,也就是说越位于链表头部的节点优先级越高,相反位于越位于链表尾部的节点优先级越低,在删除的时候优先删除链表尾部的数据,这么听好像很有道理但是更新的时间复杂度能够做到o(1)吗?好像不行对吧,因为链表不支持随机访问,所以我们得循环遍历从而找到要更新的节点,那么这一步时间复杂度就不是o(1)了更何况后面的步骤,所以我们这里得进行改进,哈希表可以帮助我们在o(1)的时间复杂度找到想要的数据,那要想更新的问题我们就得在哈希表找到数据的同时找到数据对应在链表上的位置,那么这里的做法就是在修改哈希表中记录的元素类型,由原来的int改为list类中的迭代器类型,那么这里的代码就如下:

unordered_map<int, list<pair<int,int>>::iterator> _hashmap;
list<pair<int,int>> _l1;

哈希表中的元素有一个元素记录着链表中相应元素的位置,这样我们就可以使用O(1)的时间找到哈希表中的元素还顺便找到了链表中的元素,构造函数里面提供了一个名为capacity的变量,表示这个容器能够存储数据的个数,那么为了方便我们后面删除数据,这里就再添加一个数据表示当然LRU的容量,并且再使用typedef来重命名依次减轻代码的长度,然后构造函数里面只用对容量变量进行初始化就行那么这里的代码就如下:

class LRUCache {
public:
typedef list<pair<int,int>>::iterator LiIter;
    LRUCache(int capacity) 
        :_capacity(capacity)
    {}
    
    int get(int key) {

    }
    
    void put(int key, int value) {

    }
private:
    unordered_map<int,LiIter> _hashmap;
    list<pair<int,int>> _l1;
    size_t _capacity;
};

get函数非常好实现,首先使用find函数来查找当前的数据在还是不在,那么这里我们可以使用find函数来实现,如果find函数的返回值为哈希表的end的话就说明当前数据不存在,如果不为end的话我们就先通过操作->来得到内部的第二个数据,但是这里的数据依然为一个指向链表的迭代器,所以这里还要通过操作符->来获取第二个数据才是我们想要的,那么这里的代码就下:

int get(int key) {
    auto ret=_hashmap.find(key);
    if(ret!=_hashmap.end())
    {
        return ret->second->second;
    }
    else
    {
        return -1;
    } 
}

但是这里并没有结束,因为我们查找了这里的数据,所以这个数据的优先级应该要被改变,也就是说将元素所在的链表位置进行该表,那么这里有两种方法第一种就是先记录当前的元素,然后将元素进行删除并在链表的头部插入一个相同的数据,最后修改迭代器的指向,但是这种方法太麻烦了我们可以直接使用容器中的splice函数来实现节点的转移,这个函数的参数形式和功能介绍如下:
在这里插入图片描述
在这里插入图片描述
我们可以将自己链表中的节点转移到自己链表的头部位置,那么这里的代码就如下:

int get(int key) {
    auto ret=_hashmap.find(key);
    if(ret!=_hashmap.end())
    {
        LiIter it =ret->second;
        _l1.splice(_l1.begin(),_l1,it);
        return it->second;
    }
    else
    {
        return -1;
    }
}

put函数分为两种情况:一个是新增一个是插入,我们首先来判断一下当前的数据在还是不在如果在的话就是新增,如果不在的话就是插入,那么这里可以通过find函数的返回值来进行判断:

void put(int key, int value) {
    auto ret=_hashmap.find(key);
    if(ret!=_hashmap.end())
    {
        //元素存在那么这里就是更新
    }
    else
    {
        //元素不存在这里是插入
    }
}

如果当前是插入的话这里得进行判断,但是这里判断的时候不能使用list的size函数而是得使用哈希的size函数,因为list中的size是顺序遍历时间复杂度为o(N),而哈希中的size是直接返回内部的数据,所以当对象满了之后我们就得删除数据,首先创建变量记录链表的尾部数据,然后使用哈希的erase函数删除该数据,最后使用pop_back函数删除链表的尾部数据,那么这里的代码如下:

void put(int key, int value) {
   auto ret=_hashmap.find(key);
   if(ret!=_hashmap.end())
   {
       //元素存在那么这里就是更新
   }
   else
   {
       //元素不存在这里是插入
       if(_capacity==_hashmap.size())
       {
           //满了就要删除
           pair<int,int> tmp=_l1.back();
           _l1.pop_back();
           _hashmap.erase(tmp.second);
       }
   }
}

将数据删除之后就往链表的头部插入数据,然后再往哈希表里面插入数据并将该元素的第二个元素初始化为容器开头的位置,那么这里的代码就如下:

void put(int key, int value) {
    auto ret=_hashmap.find(key);
    if(ret!=_hashmap.end())
    {
        //元素存在那么这里就是更新
    }
    else
    {
        //元素不存在这里是插入
        if(_capacity==_hashmap.size())
        {
            //满了就要删除
            pair<int,int> tmp=_l1.back();
            _l1.pop_back();
            _hashmap.erase(tmp.second);
        }
        _l1.push_front(make_pair(key,value));
        _hashmap[key]=_l1.begin();
    }
}

如果当前的对象没有满的话我们就要更新value的值和链表中当前元素所在的位置,那么这里的思路和前面的一致,只不过得修改一下存储的value即可,那么这里的代码如下:

    void put(int key, int value) {
        auto ret=_hashmap.find(key);
        if(ret!=_hashmap.end())
        {
            //元素存在那么这里就是更新
             LiIter it =ret->second;
             it->second=value;
            _l1.splice(_l1.begin(),_l1,it);
        }
        else
        {
            //元素不存在这里是插入
            if(_capacity==_hashmap.size())
            {
                //满了就要删除
                pair<int,int> tmp=_l1.back();
                _l1.pop_back();
                _hashmap.erase(tmp.second);
            }
            _l1.push_front(make_pair(key,value));
            _hashmap[key]=_l1.begin();
        }
    }

写到这里我们的代码就完成了,那么完整的代码如下:

class LRUCache {
public:
typedef list<pair<int,int>>::iterator LiIter;
    LRUCache(int capacity) 
        :_capacity(capacity)
    {}
    
    int get(int key) {
        auto ret=_hashmap.find(key);
        if(ret!=_hashmap.end())
        {
            LiIter it =ret->second;
            _l1.splice(_l1.begin(),_l1,it);
            return it->second;
        }
        else
        {
            return -1;
        }
    }
    
    void put(int key, int value) {
        auto ret=_hashmap.find(key);
        if(ret!=_hashmap.end())
        {
            //元素存在那么这里就是更新
             LiIter it =ret->second;
             it->second=value;
            _l1.splice(_l1.begin(),_l1,it);
        }
        else
        {
            //元素不存在这里是插入
            if(_capacity==_hashmap.size())
            {
                //满了就要删除
                pair<int,int> tmp=_l1.back();
                _l1.pop_back();
                _hashmap.erase(tmp.first);
            }
            _l1.push_front(make_pair(key,value));
            _hashmap[key]=_l1.begin();
        }
    }
private:
    unordered_map<int,LiIter> _hashmap;
    list<pair<int,int>> _l1;
    size_t _capacity;
};

题目测试的结果如下:
在这里插入图片描述
那么这就说明我们的代码没有问题,本篇文章到此结束。

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

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

相关文章

Mysql-事务、视图和索引

目录 事务 操作 步骤 特性 并发事务问题 事务隔离级别 索引 作用 分类 主键索引&#xff08;PRIMARY KEY&#xff09; 唯一索引&#xff08;UNIQUE&#xff09; 常规索引&#xff08;INDEX&#xff09; 全文索引&#xff08;FULLTEXT&#xff09; 管理索引 数据…

2023-07-28 LeetCode每日一题(并行课程 III)

2023-07-28每日一题 一、题目编号 2050. 并行课程 III二、题目链接 点击跳转到题目位置 三、题目描述 给你一个整数 n &#xff0c;表示有 n 节课&#xff0c;课程编号从 1 到 n 。同时给你一个二维整数数组 relations &#xff0c;其中 relations[j] [prevCoursej, next…

CS5212/CS5202|国产DP转VGA芯片|DP转VGA单转方案芯片

CS5212/CS5202是集睿致远推出的一款国产DP转VGA方案芯片&#xff0c;可替代替代RTD2166&#xff0c;替代IT6516芯片方案&#xff0c;相比之下CS5212/CS5202外围器件少&#xff0c;设计版框尺寸小。 CS5212/CS5202 DP转VGA方案原理图 CS5202特性 支持2通道数字输入&#xff0c…

一封来自Java学姐的信

黑马JavaEE学科学姐想对学弟学妹们说&#xff1a;勤学如春起之苗&#xff0c;不见其增&#xff0c;日有所长。 辍学如磨刀之石&#xff0c;不见其损&#xff0c;日有所亏。 学科 | JavaEE 校区 | 太原 亲爱的学弟学妹们&#xff0c;在学校“混日子”的时间很快就过去了&…

虚拟演播室:Aximmetry Broadcast DE 2023 Crack

Aximmetry 的基于节点的编辑器可以根据您的特定项目进行塑造&#xff0c;从而在实现项目时提供更大的灵活性&#xff1b;无论是广播制作、虚拟活动、预可视化、基于 LED 墙的虚拟制作还是您可以想象的任何其他 3D 图形任务。有关 XR 的更多信息。Aximmetry 还拥有自己的先进色度…

matlab BP神经网络对iris数据集进行分类

iris数据集 本文所用数据集&#x1f449;&#x1f449;&#x1f449;iris分类数据集 1.数据预处理 %% 1.数据预处理 oridatareadtable(Iris.xls,Sheet,Sheet1); Xtable2array(oridata(:,(1:4))); % X转化为array类型 Ytable2array(oridata(:,5)); % Y因为包含中文字符&…

如何彻底卸载VMware

目录 第一章、停止并卸载VMware程序1.1&#xff09;停止VMware有关的服务1.2&#xff09;打开任务管理器停止进程1.3&#xff09;卸载VMware程序 第二章、残留文件删除2.1&#xff09;打开注册表2.2&#xff09;删除注册表残留文件2.3&#xff09;C盘文件删除 友情提醒&#xf…

单机环境下定时任务的基本原理和常见解决方案(二)之时间轮原理分析

单机环境下定时任务的基本原理和常见解决方案之时间轮原理分析 时间轮Netty时间轮使用Netty 时间轮 HashedWheelTimer 源码分析向时间轮里添加taskWorkerThread线程执行时间轮任务 多层时间轮总结 时间轮 生活中的时钟想必大家都不陌生&#xff0c;而时间轮的设计思想就是来源…

【3】-使用@task设置测试用例执行的权重

多个测试链路压测使测试任务按预想的比例执行 locust的task装饰器提供了入参weight&#xff0c;locust执行测试任务时&#xff0c;会根据weight的比例进行分配用户数 from locust import task, HttpUserclass MyTestUser(HttpUser):# test_01 : test_02 3 : 1task(3)def wei…

数据结构:分块查找

分块查找&#xff0c;也叫索引顺序查找&#xff0c;算法实现除了需要查找表本身之外&#xff0c;还需要根据查找表建立一个索引表。例如图 1&#xff0c;给定一个查找表&#xff0c;其对应的索引表如图所示&#xff1a; 图 1 查找表及其对应的索引表 图 1 中&#xff0c;查找表…

安全测试国家标准解读——资源管理和内存管理

下面的系列文章主要围绕《GB/T 38674—2020 信息安全技术 应用软件安全编程指南》进行讲解&#xff0c;该标准是2020年4月28日&#xff0c;由国家市场监督管理总局、国家标准化管理委员会发布&#xff0c;2020年11月01日开始实施。我们对该标准中一些常见的漏洞进行了梳理&…

c++学习(哈希)[21]

哈希 哈希表&#xff08;Hash Table&#xff09;&#xff0c;也称为散列表&#xff0c;是一种常用的数据结构&#xff0c;用于实现键值对的存储和查找。它通过将键映射到一个索引位置来快速地访问和操作数据。 哈希表的基本思想是使用一个哈希函数将键映射到一个固定范围的整…

自定义 View(六) 自定义评分星星

先看看效果图 1.自定义 View 的基本流程 创建 View Class创建 attr 属性文件&#xff0c;确定属性View Class 绑定 attr 属性onMeasure 测量onDraw 绘制onTouchEvent ( 用户交互需要处理 ) 1.1 创建 View Class package com.example.view_day05_ratingbar;import android.…

LabVIEW实现三相异步电机磁通模型

LabVIEW实现三相异步电机磁通模型 三相异步电动机由于经济和出色的机电坚固性而广泛用于工业化应用。这台机器的设计和驱动非常简单&#xff0c;但在控制扭矩和速度方面&#xff0c;它隐藏了相当大的功能复杂性。通过数学建模&#xff0c;可以理解机器动力学。 基于微分方程的…

day44-Custom Range Slider(自定义范围滑块)

50 天学习 50 个项目 - HTMLCSS and JavaScript day44-Custom Range Slider&#xff08;自定义范围滑块&#xff09; 效果 index.html <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewp…

vue中tab隐藏display:none(v-show无效,v-if有效)

目录 背景 原因&#xff1a;display: table-cell>display:none 解决&#xff1a; 方法A.获取元素设置display&#xff08;适用于 简单场景&#xff09; 方法B.自定义tabs​​​​​​​ &#xff08;适用于 复杂场景&#xff09; 背景 内联样式(style“ ”) /this.$…

JVM简述

JDK&JRE&JVMJVM运行时内存结构图方法区堆区栈区程序计数器本地方法栈 JVM 的主要组成部分及其作用 JDK&JRE&JVM JVM就是java虚拟机&#xff0c;一台虚拟的机器&#xff0c;用来运行java代码 但并不是只有这台机器就可以的&#xff0c;java程序在运行时需要依赖…

Linux权限提升:自动化信息收集

在本文中&#xff0c;我们将介绍在基于Linux的设备上进行初始访问后&#xff0c;可用于后渗透阶段漏洞利用和枚举的一些自动化脚本。 ** 介绍** 大多数时候&#xff0c;当攻击者攻击Linux操作系统时&#xff0c;他们将获得基本的Shell&#xff0c;可以将其转换为TTY Shell或m…

apple pencil值不值得购买?便宜的电容笔推荐

如今&#xff0c;对ipad使用者而言&#xff0c;苹果原装的Pencil系列无疑是最佳的电容笔。只是不过这款电容笔的售价&#xff0c;实在是太高了&#xff0c;一般的用户都无法入手。因此&#xff0c;在具体的使用过程中&#xff0c;如何选用一种性能优良、价格低廉的电容笔是非常…

Jmeter+验证json结果是否正确小技巧

前言&#xff1a; 通过sql语句或者返回的参数&#xff0c;可以在查看结果树返回的结果中&#xff0c;用方法先跑一下验证是否取到自己想要的值 步骤&#xff1a; 1、添加查看结果树 2、跑出结果 3、在查看结果树中 text改成选Json Path Tester 返回的值如果是列表里面的字符…