LRU 缓存

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) 插入/删除 。不同于 vectorlist 在头部或中间操作不需要挪动其他元素。
std::unordered_map unordered_map<int, list<...>::iterator> index; 哈希表 。键为 key,值为指向链表节点的迭代器 O(1) 查找。这是实现 LRU 快如闪电的关键,通过哈希索引直接定位链表节点。
std::pair pair<int, int> 二元组 。将 keyvalue 捆绑在一起存储。 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) {} // 直接创建并初始化

为了让你彻底理解这段代码是怎么"动"起来的,我们假设执行以下指令序列,看看内存中发生了什么:

  1. LRUCache cache(2); (容量为 2)

  2. cache.put(1, 10);

  3. cache.put(2, 20);

  4. cache.get(1);

  5. cache.put(3, 30);


运行步骤详解

第一步:初始化 LRUCache cache(2)
  • 链表 v : 为空 []

  • 哈希表 index : 为空 {}

  • 容量 : 2

第二步:执行 put(1, 10)
  1. 检查 index:没有 key 1。

  2. 插入链表 : v.emplace_front(1, 10) \\rightarrow 链表变为 [(1, 10)]

  3. 更新哈希表 : index[1] 指向链表第一个节点。

  • 状态 : v: [(1, 10)], index: {1: 节点1的地址}
第三步:执行 put(2, 20)
  1. 检查 index:没有 key 2。

  2. 插入链表 : v.emplace_front(2, 20) \\rightarrow 链表变为 [(2, 20), (1, 10)]

  3. 更新哈希表 : index[2] 指向链表第一个节点(值为 20 的那个)。

  • 状态 : v: [(2, 20), (1, 10)], index: {1: ptr1, 2: ptr2}
第四步:执行 get(1) (关键步骤:激活)
  1. 检查 index:存在 key 1。

  2. 激活节点 : 调用 v.splice(v.begin(), v, index[1])

    • 将 key 1 的节点从链表后面"剪切",贴到最前面。

    • 链表顺序从 [(2, 20), (1, 10)] 变为 [(1, 10), (2, 20)]

  3. 返回结果 : 返回 10

  • 状态 : 此时 1 变成了"最近使用",2 变成了"最久未用"。
第五步:执行 put(3, 30) (关键步骤:淘汰)
  1. 检查 index:没有 key 3。

  2. 插入头部 : v.emplace_front(3, 30) \\rightarrow 链表变为 [(3, 30), (1, 10), (2, 20)]

  3. 更新哈希表 : index[3] 指向新节点。

  4. 触发淘汰 : 此时 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)

相关推荐
blackicexs1 小时前
第八周第五天
数据结构·c++·算法
whycthe2 小时前
c++二叉树详解
数据结构·c++·算法
郝学胜-神的一滴2 小时前
循环队列深度剖析:从算法原理到C++实现全解析
开发语言·数据结构·c++·算法·leetcode
Via_Neo2 小时前
接雨水问题 + 输入优化
java·开发语言·算法
plus4s2 小时前
3月13日(进阶5)
算法
x_xbx2 小时前
LeetCode:27. 移除元素
数据结构·算法·leetcode
云泽8083 小时前
C++ map 底层探秘:从结构设计到 operator [] 实现的全解析
数据结构·c++·算法
小O的算法实验室3 小时前
2026年EAAI SCI1区TOP,基于LLM驱动的多群粒子群算法动态通信策略生成方法,深度解析+性能实测
算法·论文复现·智能算法·智能算法改进
午彦琳3 小时前
leetcode hot 100_49,128
算法·leetcode·职场和发展