当前位置: 首页 > news >正文

力扣——146.LRU缓存详解

前言前文已详细讲解LRU缓存底层原理、淘汰规则与设计思想零基础入门可跳转上篇文章LRU缓存机制题目整体设计与思路LRU 缓存机制可以通过哈希表辅以双向链表实现我们用一个哈希表和一个双向链表维护所有在缓存中的键值对。双向链表按照被使用的顺序存储了这些键值对靠近头部的键值对是最近使用的而靠近尾部的键值对是最久未使用的。哈希表即为普通的哈希映射HashMap通过缓存数据的键映射到其在双向链表中的位置。这样以来我们首先使用哈希表进行定位找出缓存项在双向链表中的位置随后将其移动到双向链表的头部即可在 O(1) 的时间内完成 get 或者 put 操作。具体的方法如下对于 get 操作首先判断 key 是否存在如果 key 不存在则返回 −1如果 key 存在则 key 对应的节点是最近被使用的节点。通过哈希表定位到该节点在双向链表中的位置并将其移动到双向链表的头部最后返回该节点的值。对于 put 操作首先判断 key 是否存在如果 key 不存在使用 key 和 value 创建一个新的节点在双向链表的头部添加该节点并将 key 和该节点添加进哈希表中。然后判断双向链表的节点数是否超出容量如果超出容量则删除双向链表的尾部节点并删除哈希表中对应的项如果 key 存在则与 get 操作类似先通过哈希表定位再将对应的节点的值更新为 value并将该节点移到双向链表的头部。上述各项操作中访问哈希表的时间复杂度为 O(1)在双向链表的头部添加节点、在双向链表的尾部删除节点的复杂度也为 O(1)。而将一个节点移到双向链表的头部可以分成「删除该节点」和「在双向链表的头部添加节点」两步操作都可以在 O(1) 时间内完成。在双向链表的实现中使用一个伪头部dummy head和伪尾部dummy tail标记界限这样在添加节点和删除节点的时候就不需要检查相邻的节点是否存在。双向链表结构体设计与分析// 自定义双向链表节点structDLinkedNode{intkey,value;// 存储键、值DLinkedNode*prev;// 前驱指针指向前一个节点DLinkedNode*next;// 后继指针指向后一个节点// 无参构造初始化空节点所有成员置空DLinkedNode():key(0),value(0),prev(nullptr),next(nullptr){}// 有参构造直接传入key、value快速创建数据节点DLinkedNode(int_key,int_value):key(_key),value(_value),prev(nullptr),next(nullptr){}};节点必须存储key后续淘汰尾部节点时需要拿到 key 去哈希表中删除对应映射只存value会导致映射无法清除构造函数统一将指针初始化为空避免野指针报错LRU缓存类私有成员变量设计与分析classLRUCache{private:unordered_mapint,DLinkedNode*cache;// 核心哈希映射key - 链表节点DLinkedNode*head;// 虚拟头哨兵节点DLinkedNode*tail;// 虚拟尾哨兵节点intsize;// 当前缓存实际存放元素数量intcapacity;// 缓存最大容纳容量cache 实现快速查找绕过链表遍历满足O(1)查找head/tail 纯虚拟节点不存储业务数据仅用来统一链表操作size/capacity 实时统计数量判断是否触发缓存淘汰机制构造函数初始化逻辑public:// 初始化LRU缓存传入最大容量LRUCache(int_capacity):capacity(_capacity),size(0){// 创建伪头部、伪尾部两个哨兵节点headnewDLinkedNode();tailnewDLinkedNode();// 空链表初始化头节点与尾节点互相绑定head-nexttail;tail-prevhead;}执行逻辑初始化最大容量 capacity 初始元素个数 size 置0开辟内存创建虚拟头尾节点初始状态链表结构 head ---- tail 无任何业务数据哨兵节点初始化完成后后续所有增删逻辑无需判断链表是否为空四大核心辅助函数所有 get 、 put 业务逻辑全部依赖这四个函数吃透指针操作即可掌握全部逻辑1. addToHead 节点插入链表头部// 将指定节点插入到链表最前端最近使用位置voidaddToHead(DLinkedNode*node){node-prevhead;// 新节点前驱指向虚拟头节点node-nexthead-next;// 新节点后继指向原本头部第一个真实节点head-next-prevnode;// 原头部节点的前驱指向当前新节点head-nextnode;// 虚拟头节点后继改为指向新节点}新写入数据、刷新访问数据统一放到链表头部。2.removeNode 摘除链表中的任意节点// 把某个节点从链表中单独摘除不删除内存voidremoveNode(DLinkedNode*node){// 前节点直接连接后节点跳过当前nodenode-prev-nextnode-next;// 后节点直接连接前节点跳过当前nodenode-next-prevnode-prev;}只断开节点前后指针关系不释放内存为移动节点做铺垫。3.moveToHead 将节点移动到头部// 将已存在的节点移动至头部刷新为最近使用voidmoveToHead(DLinkedNode*node){removeNode(node);// 第一步先从原位置摘除节点addToHead(node);// 第二步重新插入到链表头部}先摘后插是LRU更新访问顺序的标准写法。4.removeTail删除尾部最久未使用节点// 删除链表末尾节点最久未使用数据并返回该节点DLinkedNode*removeTail(){// 虚拟尾节点的前驱就是最后一个真实业务节点DLinkedNode*nodetail-prev;removeNode(node);returnnode;}专门用于缓存容量溢出时执行淘汰策略。get 查询方法实现逻辑// 根据key查询缓存数据intget(intkey){// 哈希表中不存在该key直接返回-1if(!cache.count(key)){return-1;}// key存在通过哈希表O(1)定位节点DLinkedNode*nodecache[key];// 访问即代表最近使用移动到链表头部moveToHead(node);// 返回节点存储的值returnnode-value;}逻辑流程哈希快速判重不存在直接返回 -1存在则定位节点访问必刷新位置符合LRU最近使用规则返回对应数值put存入/更新方法实现逻辑voidput(intkey,intvalue){// 分支1当前key不存在执行新增缓存逻辑if(!cache.count(key)){// 1. 创建全新数据节点DLinkedNode*nodenewDLinkedNode(key,value);// 2. 存入哈希表建立映射cache[key]node;// 3. 新数据默认为最近使用插入头部addToHead(node);// 4. 缓存元素数量1size;// 5. 判断是否超出最大容量超出则淘汰最久未使用数据if(sizecapacity){// 摘除尾部淘汰节点DLinkedNode*removedremoveTail();// 同步删除哈希表中对应key映射cache.erase(removed-key);// 手动释放内存杜绝内存泄漏deleteremoved;// 元素数量-1--size;}}// 分支2当前key已存在执行更新逻辑else{// 1. 哈希表定位旧节点DLinkedNode*nodecache[key];// 2. 覆盖更新存储的值node-valuevalue;// 3. 更新访问位置移至头部moveToHead(node);}}两大分支逻辑新增数据创建节点 → 绑定哈希映射 → 头部插入 → 数量自增超容量自动删除尾部节点同步清哈希、释放内存更新旧数据直接修改value值 → 刷新访问位置即可不改变缓存数量整体代码structDLinkedNode{intkey,value;DLinkedNode*prev;DLinkedNode*next;DLinkedNode():key(0),value(0),prev(nullptr),next(nullptr){}DLinkedNode(int_key,int_value):key(_key),value(_value),prev(nullptr),next(nullptr){}};classLRUCache{private:unordered_mapint,DLinkedNode*cache;DLinkedNode*head;DLinkedNode*tail;intsize;intcapacity;public:LRUCache(int_capacity):capacity(_capacity),size(0){headnewDLinkedNode();tailnewDLinkedNode();head-nexttail;tail-prevhead;}intget(intkey){if(!cache.count(key)){return-1;}DLinkedNode*nodecache[key];moveToHead(node);returnnode-value;}voidput(intkey,intvalue){if(!cache.count(key)){DLinkedNode*nodenewDLinkedNode(key,value);cache[key]node;addToHead(node);size;if(sizecapacity){DLinkedNode*removedremoveTail();cache.erase(removed-key);deleteremoved;--size;}}else{DLinkedNode*nodecache[key];node-valuevalue;moveToHead(node);}}voidaddToHead(DLinkedNode*node){node-prevhead;node-nexthead-next;head-next-prevnode;head-nextnode;}voidremoveNode(DLinkedNode*node){node-prev-nextnode-next;node-next-prevnode-prev;}voidmoveToHead(DLinkedNode*node){removeNode(node);addToHead(node);}DLinkedNode*removeTail(){DLinkedNode*nodetail-prev;removeNode(node);returnnode;}};
http://www.zskr.cn/news/1339189.html

相关文章:

  • OpenStack系列第二期:认证与镜像管理
  • 【战术鸡蛋控制】鸡蛋制导控制的基本知识 — 快速精讲
  • 【协作算法】6 群体智能优化方法:从粒子协同到遗传演化的计算范式
  • 如何将企业微信 RPA 抽象为高可用的外部群自动化 API?
  • 在线课程|基于springboot+vue的在线课程管理系统(源码+数据库+文档)
  • 老合兴洋服:贵阳西服定制的匠心之选,穿出绅士的体面与尊严 - 贵州服装测评君
  • 2026年十大品牌消泡剂厂家推荐指南:懂工艺、重安全的厂家 - 奔跑123
  • TurboVNC高性能远程桌面解决方案:从入门到精通
  • 解决Claude Code频繁封号与Token不足的痛点转向Taotoken
  • 下面是一篇偏技术博客风格、但尽量通俗、好懂的逻辑回归讲解文章,你可以直接当作学习笔记或发布用草稿 ✅一文搞懂逻辑回归(Logistic Regression)
  • 联想笔记本BIOS解锁终极指南:深度解析CFG Lock关闭与DVMT显存调整
  • 智界V9,50万的豪华MPV来了
  • 5分钟激活Adobe全家桶:Adobe-GenP通用补丁终极使用指南
  • 智能体状态指示:何时思考、何时调用工具、何时出错
  • Windows和Office激活终极指南:KMS_VL_ALL_AIO一键解决方案
  • 超声波分散仪十大厂家与推荐供应商:国内优质制造企业全景展示 - 品牌推荐大师1
  • 2026年酒店装配式卫生间生产厂家行业发展与技术创新 - 品牌排行榜
  • 土方车远程监控智慧运维系统方案
  • 2026医考机构通过率对比:谁更值得选? - 医考机构品牌测评专家
  • 移动端开发(iOS/Android)简历:上架项目 + 性能优化亮点
  • 腾讯操作系统层级AI助手Marvis上线,支持三端同步且免费,还分双模式!
  • 百度文库核心功能全解析(教育博主实操版)
  • 【2024方言AI语音权威报告】:基于1762条真实东北语料实测,ElevenLabs东北话MOS得分仅3.8?这4项定制化微调让评分跃升至4.6+
  • 奖励分数越高,模型越烂?斯坦福这篇论文找到了破解之法
  • 【ElevenLabs蒙古文语音实战指南】:2024年唯一支持实时蒙古语TTS的AI语音方案深度评测
  • 2026 年广东省内医科大学院校哪所比较好?有什么报考推荐 - 品牌2025
  • 从零入门 eNSP:网络模拟器基础学习与系统化学习方案
  • 喀什外贸独立站哪家服务好?WaiMaoYa 外贸鸭打造中亚贸易专业网站 - 外贸营销工具
  • 广州俄罗斯线路代理清关公司实力排行盘点 - 互联网科技品牌测评
  • 如何在ComfyUI中使用InstantID实现AI人脸风格化:完整指南与实战技巧