LRU需要注意:
- 1、访问了某个元素之后,就要把这个元素的位置放到缓存的头部(最近使用)
- 2、向缓存写入数据是,先检查该数据是否存在,如果不存在,则在头部写入,否则就将存在的值移动到头部(因为也算是进行了访问),题目还要求了对存在的值进行修改
- 3、缓存满了之后,从缓存的尾部(最久没使用)开始删除元素以腾出空间
第一条,需要移除该元素,并将该元素放到头部,也就是快速的删除和添加,删除的元素可能在任何位置。那就最好使用链表了,链表删除/添加元素的复杂度为O(1)。但是,题目要求是先访问这个元素,链表的快速删除/添加是建立在给出待删除位置的迭代器的前提下的,那怎么找到这个元素呢?链表不能随机访问,所以只能遍历链表,复杂度为O(n),所以第一个操作复杂度就是O(n),主要是定位到相应的key
值需要遍历。
第二条,写入数组同样需要知道该key
是否存在,总不能又遍历一次吧?其实题目给出的pair<key, value>
已经暗示了使用map
了。map
可以实现O(1)的查找,就可以知道该元素是否已经存在,如果用map
指向链表:<key = key
, value = 指向链表中key为当前key
的元素的指针(迭代器)>,而链表中的元素也是一个pair<key, value>。我们在构造链表的时候随时维护一个相应的map
,就可以实现O(1)复杂度的查找以及元素定位。
第三条,删除元素的时候维护的map也要实时更新。
class LRUCache {
public:
LRUCache(int capacity) : cap(capacity) {}
int get(int key) {
if(m.count(key) != 0){
int val = m[key]->second;
l.erase(m[key]);
l.push_front({key, val});//访问过的元素移动到头部
m[key] = l.begin();
return val;
}
return -1;
}
void put(int key, int value) {
if(m.count(key) != 0){//已经存在
l.erase(m[key]);
l.push_front({key, value});
m[key] = l.begin();
}else{
if(l.size() == cap){//同步删除
m.erase(l.back().first);
l.pop_back();
}
l.push_front({key, value});
m[key] = l.begin();
}
}
private:
int cap;
list<pair<int, int>> l;
unordered_map<int, list<pair<int, int>>::iterator> m;
};
进阶,上述的lru不是线程安全的(get和put操作都不是),如果要想线程安全的话,需要加锁:
get和put函数的第一行:
std::lock_guard<std::mutex> lck(cache_mutex_);
private处:
std::mutex cache_mutex_;