[数据结构] LRU Cache | ListMap 实现

embedded/2024/12/26 8:28:07/

目录

1. 什么是 LRU Cache

2. LRU Cache 的实现

3. 代码

概念理解

题目要求

删除一个节点(抽出一本书)

在链表头添加一个节点(把一本书放在最上面)

如何快速找到要抽出来的书?

答疑

代码实现

复杂度分析

4. 测试


1. 什么是 LRU Cache

定义:

  • LRU 是 Least Recently Used(最近最少使用)的缩写,它是一种缓存替换算法

Cache 的概念:

  • 在狭义上,Cache 指的是位于 CPU 和主存之间的快速 RAM,通常使用 SRAM 技术而非 DRAM,以提供更快的数据访问速度。
  • 广义上来讲,Cache 是指任何用于协调两种速度差异较大的硬件之间数据传输速度的结构。例如,CPU 与主存、内存与硬盘、甚至硬盘与网络之间的缓存。

  • 替换策略:
    • 当缓存容量达到上限而需要加入新内容时,LRU Cache 根据“最久未使用”的原则选择要移除的内容,即移除一段时间内最久没有被访问过的数据项。
    • 这种策略确保了缓存中保留的是最常或最近使用的数据。

2. LRU Cache 的实现

题目背景:

  • 我们可以通过解决 OJ 题目 146. LRU 缓存 来学习 LRU Cache 的实现方法。此题要求设计的数据结构必须支持在平均 O(1) 时间复杂度下完成 putget 操作。

初始化:

  • LRUCache(int capacity) 使用正整数作为容量来初始化缓存。这里的容量是指可以存储的关键字数量,当插入新的关键字导致总数超过容量时,应删除最近最少使用的关键字。

操作解析:

  • Get 操作: 给定一个关键字(实际上是一个地址),检查该关键字是否存在于缓存中。如果存在,则返回对应的数据值,并更新其为最近使用。
  • Put 操作: 包含两种情况
    • 一是更新现有关键字的值
    • 二是添加新的关键字-值对。
    • 如果添加新关键字导致缓存超出容量,则需先删除最久未使用的元素。

结构设计:

  • 哈希表 (unordered_map): 为了满足查找、新增和更新均为 O(1) 的时间复杂度,我们使用哈希表存储关键字到链表节点迭代器的映射。
  • 双向链表 (list): 用来维护元素的使用顺序。每次访问某个关键字后,将对应的节点移到链表头部;当需要移除元素时,总是移除链表尾部的节点(即最近最少使用的元素)。

优化点:

  • 使用 unordered_map<int, list< pair<int, int> >::iterator> 将关键字映射到链表中的位置,这样可以保证所有操作的时间复杂度为 O(1)。
  • 利用 splice 方法直接在链表中移动节点,避免不必要的创建和销毁节点的操作,提高效率。


3. 代码

概念理解

想象你有一摞书。

  • get:把一本书(key)抽出来,放在最上面。
  • put:放入一本新书。
    • 如果已经有这本书(key),就把它抽出来放在最上面,并替换它的 value。(例如把一本书的第二版替换成第三版)
    • 如果没有这本书(key),就放在最上面。
    • 如果超过 capacity 本书,就把最下面的书移除。
题目要求
  • get 和 put 都是 O(1) 时间复杂度,这可以用双向链表实现,每个节点都表示一本书(key 和 value)。
  • 每个节点都有两个指针 prevnext 分别指向前一个和下一个节点。
  • 此外,链表中还包含一个哨兵节点 dummy,这可以让每个节点的 prevnext 都不为空,从而简化代码逻辑。
删除一个节点(抽出一本书)

  • 删除节点 x 之前:
    • x.prev.next = x.next
    • x.next.prev = x.prev
在链表头添加一个节点(把一本书放在最上面)

  • 插入节点 x 之前:
    • x.prev = dummy
    • x.next = dummy.next
    • x.prev.next = x
    • x.next.prev = x
如何快速找到要抽出来的书?
  • key 映射链表中的对应节点。这可以用哈希表实现。
答疑
  • 问:需要几个哨兵节点?
    • 答:一个就够了。一开始哨兵节点 dummyprevnext 都指向 dummy。随着节点的插入,dummynext 指向链表的第一个节点(最上面的书),prev 指向链表的最后一个节点(最下面的书)。
  • 问:为什么节点要把 key 也存下来?
    • 答:在删除链表末尾节点时,也要删除哈希表中的记录,这需要知道末尾节点的 key。
代码实现
class Node {
public:int key;int value;Node* prev;Node* next;Node(int k = 0, int v = 0): key(k), value(v), prev(nullptr), next(nullptr) {}
};class LRUCache {
private:int capacity;Node* dummy; // 哨兵节点unordered_map<int, Node*> key_to_node;void remove(Node* x) {x->prev->next = x->next;x->next->prev = x->prev;}void push_front(Node* x) {x->prev = dummy;·x->next = dummy->next;x->prev->next = x;x->next->prev = x;}Node* get_node(int key) {auto it = key_to_node.find(key);if (it == key_to_node.end()) {return nullptr;}Node* node = it->second;remove(node);push_front(node);return node;}public:LRUCache(int capacity): capacity(capacity), dummy(new Node()) {dummy->prev = dummy;dummy->next = dummy;}int get(int key) {Node* node = get_node(key);return node ? node->value : -1;}void put(int key, int value) {Node* node = get_node(key);if (node) {node->value = value;return;}key_to_node[key] = node = new Node(key, value);push_front(node);if (key_to_node.size() > capacity) {Node* back_node = dummy->prev;key_to_node.erase(back_node->key);remove(back_node);delete back_node;}}
};
复杂度分析
  • 时间复杂度:所有操作均为 O(1)。
  • 空间复杂度:O(min(p, capacity)),其中 p 为 put 的调用次数。

4. 测试

#include <iostream>
#include <unordered_map>#include"LRUCache.hpp"void testLRUCache() {LRUCache cache(2);cache.put(1, 1);cache.put(2, 2);std::cout << "Get key 1: " << (cache.get(1) == 1 ? "Pass" : "Fail") << std::endl;cache.put(3, 3);std::cout << "Get key 2: " << (cache.get(2) == 2 ? "Pass" : "Fail") << std::endl;cache.put(4, 4);std::cout << "Get key 1: " << (cache.get(1) == 1 ? "Pass" : "Fail") << std::endl;std::cout << "Get key 3: " << (cache.get(3) == 3 ? "Pass" : "Fail") << std::endl;std::cout << "Get key 4: " << (cache.get(4) == 4 ? "Pass" : "Fail") << std::endl;
}int main() {testLRUCache();return 0;
}

运行:

通过上述设计,我们可以有效地实现一个高效且符合 LRU 替换策略的缓存系统,适用于多种应用场景,如数据库查询缓存、网页服务器响应缓存等。

代码设计参考:灵神的题解


http://www.ppmy.cn/embedded/148840.html

相关文章

Matrix-Breakout 2 Morpheus靶场

第一步 信息收集 (1)寻找靶场真实ip arp-scan -l 靶场真实 ip 为192.168.152.154 (2)探测端口及服务 nmap -p- -sV 192.168.52.135 第二步 开始渗透 (1)访问web服务 http://192.168.152.154 and http://192.168.52.135:81 发现 81 端口的页面要登录 我们使用…

基于python的电子报实现思路

一种基于PDF生成电子报的思路 需求提出实现思路&#xff1a;技术路线核心代码&#xff1a; 需求提出 最近公司提出了一个电子报的需求&#xff0c;可看网上实现的思路基本上是方正系列的排版软件实现的&#xff0c;公司没必要买这么一套&#xff0c;于是按照自己的思路搞了一个…

Linux自动挂载与卸载USB设备

一、实现udev规则 创建规则&#xff1a;sudo vi /etc/udev/rules.d/usb.rules SUBSYSTEMS"usb",SUBSYSTEM"block",ACTION"add",RUN{program}"/bin/mkdir /mnt/%k",RUN{program}"/usr/bin/systemd-mount --no-block --collect …

ABAQUS纤维混凝土细观模型基于梁单元建模

钢纤维混凝土&#xff08;SFRC&#xff09;弥补了素混凝土抗裂性的不足&#xff0c;为建立钢纤维混凝土的力学本构模型&#xff0c;本案例通过CAD随机纤维3D插件建立随机分布的纤维线模型&#xff0c;并将模型导入ABAQUS内&#xff0c;通过梁单元纤维模型&#xff0c;研究细观纤…

京东零售数据可视化平台产品实践与思考

导读 本次分享题目为京东零售数据可视化平台产品实践与思考。 主要包括以下四个部分&#xff1a; 1. 平台产品能力介绍 2. 业务赋能案例分享 3. 平台建设挑战与展望 作者&#xff1a;梁臣 京东 数据产品架构师 01平台产品能力介绍 1. 产品矩阵 数据可视化产品是一种利用…

C++ OpenCV中读取YAML文件的详解:定义、用途与实用示例

C OpenCV中读取YAML文件的详解&#xff1a;定义、用途与实用示例 在计算机视觉和图像处理领域&#xff0c;OpenCV&#xff08;Open Source Computer Vision Library&#xff09;是一个广泛使用的开源库。YAML&#xff08;YAML Ain’t Markup Language&#xff09;作为一种简洁…

Maxscript如何从对象中选择底边或者顶边?

在3dMax中&#xff0c;比如选择了一些墙对象&#xff0c;如何才能使用Maxscript脚本选择所有底部边缘&#xff1f; 有很多方法&#xff0c;我们看一下下面这段代码&#xff1a; if isKindOf $ Editable_Poly do (local minZ $.min.zlocal getEdgeV $.getEdgeVertexlocal bo…

【4 以太网RDMA优化--面向可靠传输】

4 面向可靠传输的优化 在RDMA应用于高性能计算&#xff08;high performance computing&#xff0c;HPC&#xff09;时&#xff0c;IB网络中使用专用网卡和交换机并通过基于信用的流控策略构建无损网络环境。 当RDMA迁移到基于以太网构建的的数据中心网络时&#xff0c;由于普…