目录
LRU(Least Recently Used)最近少用
-
what
它是一种缓存淘汰机制,顾名思义,将最近使用次数最少的缓存淘汰
-
why
根据程序的局部性原理,最近经常使用的缓存留在内存中,而将最近不常用的缓存换出内存,是比较接近理想的一种缓存置换算法
-
where
在凡是带有缓存的场景中,LRU都可以作为最基本的缓存置换策略,例如:操作系统内存的页面置换、Redis缓存淘汰机制等等
-
how
如何设计一个简单的LRU缓存算法?
举个栗子,内存容量大小为2,按照key-value形式存储,结构的头部为最近最常使用的结点,尾部为最近最少使用的结点
LRU有两个操作:get和put,每次get和put操作都将查询的值变为最近最常使用的结点,当put容量已满时,删除最近最少使用的结点
int get(int key)
- 输入一个key值
- 如果key值不存在,则返回-1
- 如果key值存在,则将key-value键值对放到结构头部
void put(int key,int value)
- 首先查看是否存在相同key值的结点
- 如果存在,把它删除;
- 如果不存在,先查看容量是否已满
- 如果容量已经满了,从尾部删除最近最少使用的结点,为新插入结点腾出位置
- 如果容量未满,在最前面放入一个key-value的键值对
数据结构的选用
对于get操作的第2.3步
需要通过key值查找是否存在,则应该使得查找的时间复杂度为O(1),链式结构肯定不可以,因为需要遍历查找,考虑使用数组、hash
如果查找到key值对应的结点存在,需要将key-value键值对放到结构头部,则应该使得头部插入的时间复杂度为O(1),考虑使用链表
对于put操作的第1步
同get操作
对于put操作的第2步
则应该使得在任意位置删除的时间复杂度为O(1),数组结构肯定不可以,因为删除后需要向前移动补齐,考虑使用链式结构
对于put操作的第4步
则需要在尾部删除的时间复杂度为O(1),数组链表皆可
对于put操作的第5步
同get操作
综合考虑,因为需要在任意位置删除的时间复杂度为O(1),数组结构肯定是不能选用的,那么如果使用链式结构,该如何在O(1)时间找到这个要删除的位置呢?
思路是这样的,
我们需要以时间复杂度O(1)得知Cache中key值对应的结点是否存在,可以使用一个hash映射
我们需要在头插尾删时间复杂度为O(1),那么使用双向链表是最好的选择,想要在双向链表中以时间复杂度O(1)删除某个结点,则首先要找到这个结点对应的迭代器
那么这个hash映射,key值就是结点的key值,而value值,我们存放key值对应结点的迭代器,通过迭代器调用erase就可以实现以O(1)时间复杂度删除指定位置结点
它的结构图是下面酱婶儿的
使用这种方式我们可以实现以下操作的时间复杂度都是O(1):
在Cache的头插入、任意位置删除
查找key值对应的结点是否在Cache中
具体实现(附详细注释)
class LRUCache {
public:
list<pair<int,int>> cache;
unordered_map<int,list<pair<int,int>>::iterator> hash;
int cap;
LRUCache(int capacity) :cap(capacity){
}
int get(int key) {
//先看映射中有没有 没有 返回-1
if(hash.find(key) == hash.end())
return -1;
//如果有,取得结点
pair<int,int> node = *hash[key];
//通过结点的迭代器,删除
cache.erase(hash[key]);
//将结点重新插入到头部,变为最近最常使用
cache.push_front(node);
//更新hash中迭代器的位置
hash[key] = cache.begin();
//返回value值
return node.second;
}
void put(int key, int value) {
//先看映射中有没有
if(hash.find(key) != hash.end()){
//有 通过迭代器删除
cache.erase(hash[key]);
}else{
//没有 判断这次插入是否越界
if(cap == cache.size()){
//越界了
//注意顺序:通过Cache取得key值,删除hash中的该结点
//在Cache尾巴删除最近最少使用的结点
hash.erase(cache.back().first);
cache.pop_back();
}
}
//头插入
cache.push_front(make_pair(key,value));
//更新map的迭代器
hash[key] = cache.begin();
}
};