LRU缓存

其实LRU不难实现,但是要实现高效的LRU比较难,就是要实现增删查改均为O(1)
理解核心思想
想象LRU缓存就像一个只能放3本书的小书架:
- 最近看过的书要放在最左边
- 当书架满了要加新书时,把最右边(最久没看)的书扔掉
数据结构的作用(非常简单!)
list<pair<int, int>> cache:就是那个小书架 ,按阅读顺序放书list.front()= 最近看的(最左边)list.back()= 最久没看的(最右边)unordered_map<int, ...> map:是书的索引卡片 通过书名(key)快速找到书在书架上的位置 值类型是list<pair<int, int>>::iterator= 书在书架上的位置指针
关键操作拆解(对应现实动作)
1. get操作:找书看
int get(int key) {
// 1. 先查索引卡:这本书在书架上吗?
auto it = map.find(key); // it是指向索引卡条目的指针
if (it == map.end()) return -1; // 没这本书
// 2. 找到书了!把这本书抽出来放到最左边
// cache.splice(位置, 书架, 书的位置) = 把书从当前位置移到最前面
cache.splice(cache.begin(), cache, it->second);
// 3. 返回书的内容
return it->second->second; // it->second是书的位置,->second是书的内容
}
2. put操作:放书
void put(int key, int value) {
// 情况1:书已存在(更新)
auto it = map.find(key);
if (it != map.end()) {
it->second->second = value; // 更新书的内容
cache.splice(cache.begin(), cache, it->second); // 移到最前面
return;
}
// 情况2:新书,但书架满了
if (cache.size() == cap) {
// 扔掉最右边的书
int key_to_del = cache.back().first; // 最旧书的书名
map.erase(key_to_del); // 从索引卡中撕掉这张卡片
cache.pop_back(); // 把书从书架最右边扔掉
}
// 把新书放到最左边
cache.push_front({key, value}); // 书架上放新书
map[key] = cache.begin(); // 索引卡记录:书名->书的位置(最左边)
}
迭代器到底是什么?(超简单理解)
把list::iterator就想象成书签 或手指:
- 它指向链表中的某个具体位置
it->second就是通过书签拿到指向的那本书
完整模拟过程(cap=2)
操作序列:put(1,1), put(2,2), get(1), put(3,3)
1. put(1,1): 书架=[(1,1)], 索引卡:1->指向(1,1)
2. put(2,2): 书架=[(2,2),(1,1)], 索引卡:1->指向(1,1), 2->指向(2,2)
3. get(1): 找到书1,移到最前:书架=[(1,1),(2,2)]
4. put(3,3): 书架已满,扔掉最右边的(2,2)
- 先更新索引卡:删除key=2
- 书架变成[(1,1)],然后插入(3,3)到最前:[(3,3),(1,1)]
class LRUCache {
private:
int cap;
list<pair<int, int>> cache; // 书架:存放(key,value)对
unordered_map<int, list<pair<int, int>>::iterator> map; // 索引卡:key->书的位置
public:
LRUCache(int capacity) : cap(capacity) {}
int get(int key) {
// 在索引卡中找这本书
auto map_it = map.find(key);
if (map_it == map.end()) return -1; // 没找到
// map_it->second 是书在书架上的位置(迭代器)
// 把这本书移到书架最前面
cache.splice(cache.begin(), cache, map_it->second);
// 返回书的内容:map_it->second指向书,->second是value
return map_it->second->second;
}
void put(int key, int value) {
auto map_it = map.find(key);
if (map_it != map.end()) {
// 书已存在:更新内容并移到最前
map_it->second->second = value; // 更新书的内容
cache.splice(cache.begin(), cache, map_it->second);
return;
}
// 新书,但书架满了
if (cache.size() == cap) {
// 扔掉最旧的书(书架最后面)
pair<int, int> old_book = cache.back(); // 最旧的书
map.erase(old_book.first); // 从索引卡删除
cache.pop_back(); // 从书架扔掉
}
// 插入新书到书架最前面
cache.push_front({key, value});
// 更新索引卡:记录新书的位置(书架最前面)
map[key] = cache.begin();
}
};
关键记住:
list是物理书架,存实际数据unordered_map是索引卡,只存位置指针- 所有操作都要同时更新书架和索引卡