cpp
class LRUCache {
// 双向链表:存储 {key, value} 键值对
// 越靠近头部 (begin) 的数据表示最近刚被使用过,越靠近尾部 (back) 的表示最久未用
list<pair<int,int>> v;
// 哈希表:key 映射到链表中对应节点的迭代器 (指针)
// 作用:实现 O(1) 查找 key 在链表中的位置
unordered_map<int ,list<pair<int,int>>::iterator> index;
int capacity; // 缓存的最大容量
public:
// 构造函数,初始化容量
LRUCache(int capacity):capacity (capacity) {}
int get(int key) {
// 1. 如果 key 不在哈希表中,说明缓存不存在,返回 -1
if(index.count(key)==0) return -1;
// 2. 如果存在,利用 splice 将该节点移动到链表的最前面 (表示刚被访问)
// splice 操作是 O(1) 的,它只改变指针指向,不拷贝数据
v.splice(v.begin(), v, index[key]);
// 3. 返回该节点的值 (front 现在就是刚才那个节点)
return v.front().second;
}
void put(int key, int value) {
if(index.count(key)) {
// 情况 A:Key 已存在
// 1. 将旧节点移动到链表头部
v.splice(v.begin(), v, index[key]);
// 2. 更新该节点的 value 值
v.front().second = value;
} else {
// 情况 B:Key 是新加入的
// 1. 在链表头部插入新的键值对
v.emplace_front(key, value);
// 2. 在哈希表中记录这个新节点的迭代器
index[key] = v.begin();
}
// 检查是否超过容量限制
if(v.size() > capacity) {
// 1. 获取链表最后一个元素的 key (即最久没用的数据)
int last_key = v.back().first;
// 2. 从哈希表中删除该 key
index.erase(last_key);
// 3. 从链表中移除该节点
v.pop_back();
}
}
};
| STL 组件 | 代码中的具体用法 | 功能描述 | 核心优势 / 扩展知识 |
|---|---|---|---|
std::list |
list<pair<int,int>> v; |
双向链表 。存储 key-value 对,维护访问的先后顺序。 |
O(1) 插入/删除 。不同于 vector,list 在头部或中间操作不需要挪动其他元素。 |
std::unordered_map |
unordered_map<int, list<...>::iterator> index; |
哈希表 。键为 key,值为指向链表节点的迭代器。 |
O(1) 查找。这是实现 LRU 快如闪电的关键,通过哈希索引直接定位链表节点。 |
std::pair |
pair<int, int> |
二元组 。将 key 和 value 捆绑在一起存储。 |
在 put 超出容量时,通过 v.back().first 拿到 key 从而去哈希表里删掉它。 |
v.splice() |
v.splice(v.begin(), v, index[key]); |
节点转移 。将 index[key] 指向的节点移动到 v.begin()。 |
LRU 的核心 。它只改变指针指向,不拷贝、不析构对象,是性能最高的操作。 |
v.emplace_front() |
v.emplace_front(key, value); |
原位构造。在链表头部直接创建一个新节点。 | 比 push_front 效率更高,直接在目标内存构造,减少了一次临时对象的拷贝或移动。 |
v.back() |
v.back().first |
访问尾部。获取链表中"最久未访问"的那个元素。 | 返回的是引用。配合 pop_back() 使用,可以实现淘汰机制。 |
index.count() |
if(index.count(key) == 0) |
成员检查 。判断某个 key 是否已经在缓存中。 |
相比 find(),count() 在只需要知道"在不在"时语义更简洁。 |
| 迭代器 (Iterator) | list<...>::iterator |
内存指针的抽象。作为哈希表的值,指向链表中的特定节点。 | 稳定性 :list 的迭代器除非指向的节点被删除,否则在移动(splice)过程中不会失效。 |
传统写法(赋值):
C++
LRUCache(int _capacity) {
capacity = _capacity; // 先创建变量,再赋值
}
初始化列表写法(推荐):
C++
cpp
LRUCache(int capacity) : capacity(capacity) {} // 直接创建并初始化
为了让你彻底理解这段代码是怎么"动"起来的,我们假设执行以下指令序列,看看内存中发生了什么:
-
LRUCache cache(2);(容量为 2) -
cache.put(1, 10); -
cache.put(2, 20); -
cache.get(1); -
cache.put(3, 30);
运行步骤详解
第一步:初始化 LRUCache cache(2)
-
链表
v: 为空[] -
哈希表
index: 为空{} -
容量 :
2
第二步:执行 put(1, 10)
-
检查
index:没有 key 1。 -
插入链表 :
v.emplace_front(1, 10)\\rightarrow 链表变为[(1, 10)]。 -
更新哈希表 :
index[1]指向链表第一个节点。
- 状态 :
v: [(1, 10)],index: {1: 节点1的地址}
第三步:执行 put(2, 20)
-
检查
index:没有 key 2。 -
插入链表 :
v.emplace_front(2, 20)\\rightarrow 链表变为[(2, 20), (1, 10)]。 -
更新哈希表 :
index[2]指向链表第一个节点(值为 20 的那个)。
- 状态 :
v: [(2, 20), (1, 10)],index: {1: ptr1, 2: ptr2}
第四步:执行 get(1) (关键步骤:激活)
-
检查
index:存在 key 1。 -
激活节点 : 调用
v.splice(v.begin(), v, index[1])。-
将 key 1 的节点从链表后面"剪切",贴到最前面。
-
链表顺序从
[(2, 20), (1, 10)]变为[(1, 10), (2, 20)]。
-
-
返回结果 : 返回
10。
- 状态 : 此时 1 变成了"最近使用",2 变成了"最久未用"。
第五步:执行 put(3, 30) (关键步骤:淘汰)
-
检查
index:没有 key 3。 -
插入头部 :
v.emplace_front(3, 30)\\rightarrow 链表变为[(3, 30), (1, 10), (2, 20)]。 -
更新哈希表 :
index[3]指向新节点。 -
触发淘汰 : 此时
v.size() (3)>capacity (2)。-
找到最久未用的元素:
v.back()是(2, 20)。 -
从哈希表删除:
index.erase(2)。 -
从链表弹出:
v.pop_back()。
-
- 最终状态 :
v: [(3, 30), (1, 10)],index: {3: ptr3, 1: ptr1}
运行逻辑总结表
| 操作 | 逻辑路径 | 对链表的影响 | 对哈希表的影响 | 复杂度 |
|---|---|---|---|---|
| Get (存在) | 查找 -> 移动到头 | splice 改变节点顺序 |
无变化(迭代器依然有效) | O(1) |
| Put (新 key) | 插入头 -> 检查容量 | emplace_front 加新点 |
增加一个 key-iterator 对 | O(1) |
| Put (已存在) | 移动到头 -> 改值 | splice 移动节点 |
无变化 | O(1) |
| 淘汰 | 删尾部 | pop_back 移除尾节点 |
erase 对应的 key |
O(1) |