leetcode - LRU缓存

server/2024/11/30 17:27:47/

什么是 LRU

LRU (最近最少使用算法),

最早是在操作系统中接触到的, 它是一种内存数据淘汰策略, 常用于缓存系统的淘汰策略.

LRU算法基于局部性原理, 即最近被访问的数据在未来被访问的概率更高, 因此应该保留最近被访问的数据.

最近最少使用的解释

LRU (最近最少使用算法), 中的 "最近" 不是其绝对值的修饰, 而是一个范围.
如: 你最近去了那些地方, 最近看了哪些书.
而不是: 离你最近的人是谁, 离你最近的座位是哪一个. 

了解了最近的意义, 那么串联起来就是: 最近使用的一堆数据中, 哪一个数据使用的是最少的

LRU原理

下面展示了 LRU 算法的基本原理.

可以看到, 在 LRU 算法中, 涉及到了对象的移动, 如果使用 数组 来作为缓存, 那么移动对象的效率很慢. 因为在这个算法中, 经常涉及到头插元素, 数组 的头插是O(n^2), 非常的慢.

所以推荐使用 双向链表 来实现.

146. LRU 缓存 - 力扣(LeetCode)

但是在题目中, 要求查找和插入的时间复杂度为O(1);
双向链表的插入删除时间复杂度为O(1), 但是查找的时间复杂度为O(n).

双向链表 + 哈希表

单使用双向链表, 查找的时间复杂度为O(n), 那么数据结构的查找操作的时间复杂度为O(1)?
答案很明显: 哈希表

 定义链表节点 ListNode

struct ListNode
{
public:ListNode(){}ListNode(int k, int v):key(k),value(v){}~ListNode(){}int key;int value;// 节点中不仅存储 value, 还存储 key, 这在后面的 put 函数中有用ListNode* next;ListNode* prev;
};

LRUcache 成员属性

class LRUCache {
public:int _size = 0; // 记录缓存中已经缓存了多少数据int _capacity = 0; // 记录缓存大小 (可缓存的数据个数)ListNode* head = nullptr; // 双向链表的头节点ListNode* tail = nullptr; // 双向链表的尾节点unordered_map<int, ListNode*> table;// 底层是通过 hashtable 实现的map, 用来通过 kev 查找节点
}

LRUcache 成员方法

构造 / get / put 函数

class LRUCache {
public:LRUCache(int capacity) {_capacity = capacity; // 记录缓存的大小// 初始化链表的 头节点 和 尾节点head = new ListNode;tail = new ListNode;// 将头尾节点连接起来head->next = tail;head->prev = tail;tail->next = head;tail->prev = head;}// 通过 key 获取对应的 value. 如果 key 不存在, 则返回 -1int get(int key) {auto it = table.find(key); // 通过 hashtable 查找 key 是否存在if(it == table.end()){return -1; // 不存在对应的 [key, value], 返回 -1}// 存在 key, 记录value, 然后更新这个节点, 将这个节点移动到链表头部int ret = it->second->value;MoveToHead(it->second); // 将这个节点移动到头部return ret;}// 插入一对键值对 [key, value]void put(int key, int value) {auto it = table.find(key); // 在 hashtable 中查找是否已经存在 keyif(it != table.end()) // 已经存在 key 则更新节点的值, 并且将这个节点移动到链表头部{// 更新节点it->second->value = value;MoveToHead(it->second); // 将节点移动到链表头部return; // 直接返回, 下面是进行插入的操作}// key 不存在, 判断 空间是否已满, 满了就需要删除 链表末尾的节点if(_size == _capacity){// ListNode 中记录的 key 就起作用了, 如果只有 value, 那么就还需要遍历 tableint back = tail->prev->key;table.erase(back); // 删除 hashtable 中这个节点的记录pop_back(); // 删除尾部节点--_size;}// 链表末尾的节点已被删除, 现在需要向 链表头部 插入 新的节点ListNode* node = push_front(key, value);table[key] = node; // 在 hashtable 中记录这个新的节点++_size;}
};

MoveToHead / push_front / pop_back 函数

class LRUCache {
public:// 将 node 移动到链表头部void MoveToHead(ListNode* node){if(node == head->next) // 如果这个节点就是头部, 那么就不移动{return;}ListNode* node_next = node->next; // 记录 node 节点的后一个节点ListNode* node_prev = node->prev; // 记录 node 节点的前一个节点node_prev->next = node_next; // 将 node 的前后节点连接起来node_next->prev = node_prev;// 将 node 节点链接到链表首部node->prev = head; node->next = head->next;head->next->prev = node;head->next = node;}// 头插ListNode* push_front(int key, int value){ListNode* node = new ListNode(key, value);ListNode* next = head->next;head->next = node;node->prev = head;next->prev = node;node->next = next;return node;}// 尾删void pop_back(){ListNode* prev = tail->prev->prev;ListNode* cur = tail->prev;prev->next = tail;tail->prev = prev;delete cur;}
};

 

 

完整代码

class LRUCache {
public:struct ListNode{public:ListNode(){}ListNode(int k, int v):key(k),value(v){}~ListNode(){}int key;int value;ListNode* next;ListNode* prev;};int _size = 0;int _capacity = 0;ListNode* head = nullptr;ListNode* tail = nullptr;unordered_map<int, ListNode*> table;LRUCache(int capacity) {_capacity = capacity;head = new ListNode;tail = new ListNode;head->next = tail;head->prev = tail;tail->next = head;tail->prev = head;}int get(int key) {auto it = table.find(key);if(it == table.end()){return -1;}int ret = it->second->value;MoveToHead(it->second); // 将这个节点移动到头部return ret;}void put(int key, int value) {auto it = table.find(key);if(it != table.end()){// 更新节点it->second->value = value;MoveToHead(it->second);return;}if(_size == _capacity){int back = tail->prev->key;table.erase(back); // 删除 hashtable 中的键值对pop_back(); // 删除尾部节点--_size;}ListNode* node = push_front(key, value);table[key] = node;++_size;}void MoveToHead(ListNode* node){if(node == head->next){return;}ListNode* node_next = node->next;ListNode* node_prev = node->prev;node_prev->next = node_next;node_next->prev = node_prev;node->prev = head;node->next = head->next;head->next->prev = node;head->next = node;}ListNode* push_front(int key, int value){ListNode* node = new ListNode(key, value);ListNode* next = head->next;head->next = node;node->prev = head;next->prev = node;node->next = next;return node;}void pop_back(){ListNode* prev = tail->prev->prev;ListNode* cur = tail->prev;prev->next = tail;tail->prev = prev;delete cur;}};


http://www.ppmy.cn/server/146203.html

相关文章

代码随想录第十一天|栈与队列part02--150.逆波兰表达式求值、239.滑动窗口最大值、347.前k个高频元素、栈与队列总结篇

150.逆波兰表达式求值&#xff08;150.逆波兰表达式求值&#xff09; 题目分析&#xff1a; 计算逆波兰表达式&#xff08;后缀表达式&#xff1a;左右中&#xff09;的值&#xff0c;算符仅包含四则运算&#xff0c;操作数为一个整数或另一个表达式&#xff0c;整数除法向零…

多头注意力机制:从原理到应用的全面解析

目录 什么是多头注意力机制&#xff1f; 原理解析 1. 注意力机制的核心公式 2. 多头注意力的扩展 为什么使用多头注意力&#xff1f; 实际应用 1. Transformer中的应用 2. NLP任务 3. 计算机视觉任务 PyTorch 实现示例 总结 近年来&#xff0c;“多头注意力机制&…

【bug】AttributeError: module ‘openai‘ has no attribute ‘error’

【bug】AttributeError: module ‘openai’ has no attribute error’ 环境 openai 1.12.0详情 在处理 openai 请求异常的时候弹出该错误。 import openai# 错误 except openai.error.RateLimitError as e:xxx原因是新版 openai 库改变了API请求异常处理类的导入路径。 解…

计算机网络常见面试题总结(上)

计算机网络基础 网络分层模型 OSI 七层模型是什么&#xff1f;每一层的作用是什么&#xff1f; OSI 七层模型 是国际标准化组织提出的一个网络分层模型&#xff0c;其大体结构以及每一层提供的功能如下图所示&#xff1a; 每一层都专注做一件事情&#xff0c;并且每一层都需…

如何借助AI生成PPT,让创作轻松又高效

PPT是现代职场中不可或缺的表达工具&#xff0c;但同时也可能是令人抓狂的时间杀手。几页幻灯片的制作&#xff0c;常常需要花费数小时调整字体、配色与排版。AI的飞速发展为我们带来了革新——AI生成PPT的技术不仅让制作流程大大简化&#xff0c;还重新定义了效率与创意的关系…

KMP算法

提示&#xff1a;文章 文章目录 前言一、背景二、KMP算法2.1 PMT计算方式2.2 next数组计算方法2.3 问题探究 总结 前言 前期疑问&#xff1a; 本文目标&#xff1a; 一、背景 下面的所有叙述是基于为上一个表述&#xff1a; 下面贴出别人整理的两个知识点&#xff1a; 在字符…

下载安装Android Studio

&#xff08;一&#xff09;Android Studio下载地址 https://developer.android.google.cn/studio 滑动到 点击下载文档 打开新网页 切换到english ![](https://i-blog.csdnimg.cn/direct/b7052b434f9d4418b9d56c66cdd59fae.png 等待一会&#xff0c;出现 点同意后&#xff0…

滑动窗口讲解(c基础)

滑动窗口的基本概念 滑动窗口是一种高效处理线性数据结构&#xff08;如数组、字符串&#xff09;的算法技巧。它就像是一个可移动的 “框”&#xff0c;框住数据结构中的一部分元素&#xff0c;通过不断地移动这个 “框”&#xff08;即滑动窗口&#xff09;&#xff0c;对框内…