【C++从0到王者】第四十九站:LRU Cache

文章目录

1.CPU取数据时的分层

如下所示是CPU的三级缓存,因为CPU太快了,所以设置了很多层来提高总体的效率

那么这里就涉及到一个问题,缓存空间优先使用的话,如果满了以后,要更新数据,其他数据要进入,谁出去呢?

2.LRU Cache

LRU是Least Recently Used的缩写,意思是最近最少使用,它是一种Cache替换算法。 什么是Cache?狭义的Cache指的是位于CPU和主存间的快速RAM, 通常它不像系统主存那样使用DRAM技术,而使用昂贵但较快速的SRAM技术。 广义上的Cache指的是位于速度相差较大的两种硬件之间, 用于协调两者数据传输速度差异的结构。除了CPU与主存之间有Cache, 内存与硬盘之间也有Cache,乃至在硬盘与网络之间也有某种意义上的Cache── 称为Internet临时文件夹或网络内容缓存等。

Cache的容量有限,因此当Cache的容量用完后,而又有新的内容需要添加进来时, 就需要挑选并舍弃原有的部分内容,从而腾出空间来放新内容。LRU Cache 的替换原则就是将最近最少使用的内容替换掉。其实,LRU译成最久未使用会更形象, 因为该算法每次替换掉的就是一段时间内最久没有使用过的内容。

3.LRU Cache 的实现

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

我们来看这道题

LRU Cache

代码实现如下:

cpp 复制代码
class LRUCache {
public:
    LRUCache(int capacity) 
        :_capacity(capacity)
    {}
    
    int get(int key) {
        auto ret = _hashmap.find(key);
        if(ret != _hashmap.end())
        {
            LIterator it = ret->second;
            //转移迭代器
            _LRUList.splice(_LRUList.begin(), _LRUList, it);
            return it->second;
        }
        else
        {
            return -1;
        }
    }
    
    void put(int key, int value) {
        auto ret = _hashmap.find(key);
        //原来没有,要新增了。
        if(ret == _hashmap.end())
        {
            //容量已满,需要先清理
            if(_capacity == _hashmap.size())
            {
                //哈希表的删除要用到对应的key,我们要用的是链表的最后一个key值
                //这里获取链表的最后一个结点的key
                pair<int, int> back = _LRUList.back();
                //删除哈希表的值
                _hashmap.erase(back.first);
                //链表的尾删
                _LRUList.pop_back();    
            }
            //删完后开始添加数据
            _LRUList.push_front(make_pair(key, value));
            _hashmap[key] = _LRUList.begin();
        }
        //如果原来就有key,那么先将key所对应的value进行替换掉,然后换一下顺序即可
        else
        {
            //修改一下原来的这个数据
            LIterator it = ret->second;
            it->second = value;
            _LRUList.splice(_LRUList.begin(), _LRUList, it);
        }
    }
private:
    typedef list<pair<int, int>>::iterator LIterator; 
    unordered_map<int, LIterator> _hashmap;
    list<pair<int, int>> _LRUList;
    int _capacity;
};

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

我们的思路如下所示

  • 首先我们的容器使用list和unordered_map两个,其次需要一个变量_capacity记录当前Cache的容量。因为我们的需求是get函数和push函数的效率都是O(1)。首先哈希表是可以确保它的插入和获取都是O(1)的。但是因为其是无序的,无法实现LRU的思想。所以我们需要使用一个list来进行辅助。对于list,我们可以这样设计,总是让他的最后一个结点是将要被淘汰的。也就是按照使用的时间进行排序。对于这样的思路,我们可以只利用list的头插和尾删进行实现。而这两个的效率都是O(1)。不过问题是如何将这两个容器给关联起来,因为如果不进行关联的话,那么在哈希表中找到数据以后,还要在list中去找到对应的数据将他在它自己的链表中进行头插,此时查找的效率是O(N)了。所以我们可以让数据只存储到list中,也就是list<pair<int , int>>的结构让list去存储数据,而后让哈希表中存储的key是正常的key值,但是value值存储的是链表的迭代器。让这个迭代器去指向对应的链表中的数据。这样的话,我们就可以实现,一旦在哈希表中找到该数据存在了,然后迅速找到对应的链表中的内容。去进行处理。

  • 如果是获取操作,那么我们可以将链表中的该数据原地头插,然后返回链表中的数据。我们可以发现效率都是O(1)。

  • 如果是push操作

  • 那么先要通过哈希表快速判断该数据有没有,如果不存在的话,那么就是新增操作,此时我们先要检测一下容量是否满了,如果满了,那么先要删除掉链表的最后一个数据以及哈希表中对应的数据,而在删除的时候,我们哈希表并不知道要删除哪个数据,只有链表知道,所以先要删除哈希表,然后删除链表中的数据,通过链表的back接口获取到要删除的key值,然后让哈希表删除以后,链表在进行尾删。删除好了以后就是新增了,由于哈希表的value存储的是链表的迭代器,所以只能先为链表添加,为链表头插以后,然后再将key和链表第一个结点的迭代器给哈希表。自此完成映射。

  • 如果我们一开始哈希表的判断是数据是存在的,即该key值有对应的value,那么此时我们就是一个修改操作,我们可以先通过哈希表找到该迭代器,然后将该迭代器所指向的链表的内容的第二个数据second进行修改。然后将该结点给原地头插。就可以了。

  • 对于链表的原地头插,其实在库里面就有一个splice接口。

cpp 复制代码
void splice (iterator position, list& x, iterator i);

该接口的含义是,将x链表中的i迭代器转移到position迭代器位置之前。

类似的接口还有如下

cpp 复制代码
//将x链表全部转移到position之前
void splice (iterator position, list& x);
//将x链表的[first,last)区间的迭代器放入到position之前
void splice (iterator position, list& x, iterator first, iterator last);
相关推荐
为更好遇见20 分钟前
C:内存函数
c语言·开发语言
~在杰难逃~21 分钟前
Day23笔记-Day21和Day22作业讲解&单例类
开发语言·笔记·python·pycharm·数据分析
吾爱星辰25 分钟前
【解密 Kotlin 扩展函数】自定义函数(十二)
java·开发语言·jvm·kotlin
鸽芷咕26 分钟前
【Python报错已解决】IndentationError: unexpected indent
开发语言·python·bug
BD_Marathon1 小时前
Hadoop入门两道面试题
大数据·hadoop·分布式
爱里承欢。1 小时前
【Python语言初识(一)】
开发语言·python
IT毕设梦工厂1 小时前
大数据毕业设计选题推荐-国潮男装微博评论数据分析系统-Hive-Hadoop-Spark
java·大数据·hive·hadoop·spark·毕业设计·课程设计
CN.LG2 小时前
浅谈C#之SynchronizationContext
开发语言·c#
一只小小程序猿2 小时前
Python计算机视觉编程 第九章 图像分割
开发语言·python·计算机视觉
武昌库里写JAVA2 小时前
人工智能不是人工“制”能
c语言·开发语言·数据结构·算法·二维数组