链表--LRU缓存

🔥个人主页: Milestone-里程碑

❄️个人专栏: <<力扣hot100>> <<C++>><<Linux>>

<<Git>><<MySQL>>

🌟心向往之行必能至

LRU(Least Recently Used,最近最少使用)缓存是操作系统、数据库和后端开发中的经典缓存淘汰策略,也是面试中高频手撕算法题。本文将从设计思路、数据结构选型到代码实现,一步步拆解,让你彻底掌握 LRU 缓存的核心逻辑。


一、什么是 LRU 缓存?

LRU 缓存的核心思想是:当缓存容量不足时,优先删除最近最少使用的数据,保证热点数据始终留在缓存中。

它需要满足两个核心操作:

  • get(key):查询 key 对应的 value,若不存在则返回 -1;若存在,将该数据标记为「最近使用」。
  • put(key, value):插入或更新 key-value 对;若缓存已满,先删除「最近最少使用」的数据,再插入新数据。

性能要求getput 操作的时间复杂度必须为 O(1),否则无法满足高并发场景下的性能需求。


二、数据结构选型:为什么是「双向链表 + 哈希表」?

要实现 O (1) 复杂度,单一数据结构无法满足需求,我们需要组合使用两种结构:

表格

数据结构 作用 优势
双向链表 (std::list) 维护数据的「使用时序」 头部存最近使用数据,尾部存最久未使用数据;支持 O (1) 时间内移动节点、删除尾部节点、头部插入节点
哈希表 (std::unordered_map) 快速定位数据 以 key 为键,存储对应链表节点的迭代器,实现 O (1) 时间查找任意节点

为什么不用单向链表?

单向链表无法在 O (1) 时间内删除中间节点(需要遍历找到前驱节点),而双向链表可以直接通过节点指针找到前驱,完美适配「移动节点到头部」和「删除尾部节点」的操作。

为什么哈希表要存迭代器?

迭代器是指向链表节点的「指针」,移动节点时迭代器不会失效,因此无需更新哈希表,直接复用即可,保证了操作的高效性。


三、C++ 代码实现(可直接用于面试)

cpp

运行

复制代码
#include <list>
#include <unordered_map>
using namespace std;

class LRUCache {
private:
    // 双向链表:存储 <key, value> 对,头部 = 最近使用,尾部 = 最久未使用
    list<pair<int, int>> cache_list;
    // 哈希表:key -> 链表节点迭代器,O(1) 定位节点
    unordered_map<int, list<pair<int, int>>::iterator> cache_map;
    // 缓存最大容量
    int capacity;

public:
    // 构造函数:初始化缓存容量
    LRUCache(int cap) : capacity(cap) {}

    int get(int key) {
        // 1. 在哈希表中查找 key
        auto it = cache_map.find(key);
        // 2. 若不存在,返回 -1
        if (it == cache_map.end()) return -1;
        // 3. 若存在,将节点移动到链表头部(标记为最近使用)
        cache_list.splice(cache_list.begin(), cache_list, it->second);
        // 4. 返回对应 value
        return it->second->second;
    }

    void put(int key, int value) {
        // 1. 先查找 key 是否存在
        auto it = cache_map.find(key);
        if (it != cache_map.end()) {
            // 1.1 存在:更新 value,并移动到头部
            it->second->second = value;
            cache_list.splice(cache_list.begin(), cache_list, it->second);
            return;
        }

        // 2. 不存在:插入新节点到链表头部
        cache_list.emplace_front(key, value);
        // 2.1 在哈希表中记录节点迭代器
        cache_map[key] = cache_list.begin();

        // 3. 检查容量是否超限,超限则删除最久未使用的节点(链表尾部)
        if (cache_map.size() > capacity) {
            // 3.1 先删除哈希表中的映射(通过尾部节点的 key)
            cache_map.erase(cache_list.back().first);
            // 3.2 再删除链表尾部节点
            cache_list.pop_back();
        }
    }
};

四、核心 API 深度解析

1. get(int key):查询 + 刷新使用时序

  1. 查找 :通过 cache_map.find(key) 快速定位节点,时间复杂度 O (1)。
  2. 不存在:直接返回 -1。
  3. 存在
    • 调用 splice 函数将节点从当前位置移动到链表头部,标记为「最近使用」。
    • splice 是 O (1) 操作,仅修改指针,无数据拷贝,效率极高。
    • 返回节点的 value。

2. put(int key, int value):插入 / 更新 + 淘汰逻辑

分两种场景处理:

  • 场景 1:key 已存在
    1. 更新节点的 value。
    2. 调用 splice 将节点移动到头部,刷新使用时序。
  • 场景 2:key 不存在
    1. 在链表头部插入新节点 (key, value)
    2. 在哈希表中记录 key 对应的节点迭代器。
    3. 容量检查:若缓存大小超过容量,先删除哈希表中尾部节点的 key 映射,再删除链表尾部节点(最久未使用数据)。

五、关键细节与面试考点

1. 为什么链表要存 key?

淘汰尾部节点时,需要通过节点的 key 去哈希表中删除对应的映射,否则会导致哈希表中存在无效键值对,造成内存泄漏。

2. splice 函数的妙用

cpp

运行

复制代码
cache_list.splice(cache_list.begin(), cache_list, it->second);
  • 第一个参数:目标位置(链表头部)。
  • 第二个参数:源链表(当前链表自身)。
  • 第三个参数:要移动的节点迭代器。
  • 效果:将节点从原位置移动到头部,迭代器不会失效,无需更新哈希表。

3. 淘汰顺序的保证

链表尾部始终是「最近最少使用」的数据,因此 pop_back() 直接删除尾部节点,完美符合 LRU 策略。

4. 时间复杂度分析

  • get:哈希表查找 O (1) + 节点移动 O (1) → 总复杂度 O (1)。
  • put:哈希表查找 O (1) + 节点插入 / 移动 O (1) + 容量检查 O (1) → 总复杂度 O (1)。

六、常见易错点

  1. 迭代器失效问题:移动节点时迭代器不会失效,因此哈希表无需更新;删除节点时,必须先删除哈希表映射,再删除链表节点。
  2. key 丢失问题:链表节点必须存储 key,否则无法在淘汰时删除哈希表映射。
  3. 容量判断 :判断缓存是否超限应使用 cache_map.size(),与链表大小完全一致,避免逻辑错误。

相关推荐
赵谨言6 小时前
地球磁场干扰噪声减弱声波对抗测量系统研究进展:近十年中英文文献综述
大数据·开发语言·经验分享
zhongqimeng6 小时前
中国商业联合会召开《城郊大仓基地冷库应急保障能力要求》《城郊大仓基地高效配送服务指南》团体标准审查会
大数据
Elasticsearch6 小时前
Elasticsearch BBQ:一场教科书式的向量搜索 “弯道超车”
elasticsearch
月落归舟6 小时前
排序算法---(一)
数据结构·算法·排序算法
今儿敲了吗6 小时前
DS-3 循环队列判断队满
数据结构·笔记·学习
ggabb6 小时前
中文:承载文明,引领未来
大数据·人工智能
liuyao_xianhui6 小时前
优选算法_翻转链表_头插法_C++
开发语言·数据结构·c++·算法·leetcode·链表·动态规划
尽兴-6 小时前
ElasticSearch 搜索相关性详解(含评分机制+自定义策略+多字段优化)
大数据·elasticsearch·搜索引擎·相关性·评分机制·自定义策略·多字段优化
wanhengidc6 小时前
跨境云手机适用于哪些场景
大数据·运维·服务器·数据库·科技·智能手机
木梯子6 小时前
大数据+AI+人|扑兔AI打造企业智慧经营,落地全域获客
大数据·人工智能·数据挖掘