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

算法学习之——LRU最近最少使用

概念

LRU (Least Recently Used-最近最少使用)​​ 是一种常见的缓存淘汰算法,其核心思想是:当缓存空间不足时,优先淘汰​​最久未被使用​​的数据。它基于"局部性原理"——最近被访问的数据未来更可能被再次访问。

核心特点
  1. ​有序性​​:维护数据项的访问顺序
  2. ​快速访问​​:需要快速查找、插入和删除
  3. ​淘汰机制​​:缓存满时删除最久未使用的数据
时间复杂度要求
  • 查找操作:O(1)
  • 插入操作:O(1)
  • 删除操作:O(1)
实现方案

使用 ​​双向链表 + 哈希表​​ 的组合:

  • ​双向链表​​:维护访问顺序(头节点存放最近访问,尾节点存放最久未访问)
  • ​哈希表​​:提供O(1)的键值查找能力
Java代码实现示例
import java.util.HashMap;
import java.util.Map;public class LRUCache<K, V> {// 双向链表节点class Node {K key;V value;Node prev;Node next;Node() {}Node(K key, V value) {this.key = key;this.value = value;}}// 缓存容量private final int capacity;// 哈希表用于快速查找private final Map<K, Node> cache;// 链表头尾指针private final Node head, tail;// 当前缓存大小private int size;public LRUCache(int capacity) {this.capacity = capacity;this.size = 0;cache = new HashMap<>();// 创建虚拟头尾节点(哨兵节点)head = new Node();tail = new Node();head.next = tail;tail.prev = head;}// 获取数据public V get(K key) {Node node = cache.get(key);if (node == null) return null;// 移动到链表头部(表示最近使用)moveToHead(node);return node.value;}// 添加数据public void put(K key, V value) {Node node = cache.get(key);if (node != null) {// 键已存在:更新值并移到头部node.value = value;moveToHead(node);} else {// 键不存在:创建新节点Node newNode = new Node(key, value);cache.put(key, newNode);addToHead(newNode);size++;// 超出容量时删除尾部节点if (size > capacity) {Node removed = removeTail();cache.remove(removed.key);size--;}}}// === 辅助方法 ===// 添加节点到头部private void addToHead(Node node) {node.prev = head;node.next = head.next;head.next.prev = node;head.next = node;}// 从链表中移除节点private void removeNode(Node node) {node.prev.next = node.next;node.next.prev = node.prev;}// 移动节点到头部private void moveToHead(Node node) {removeNode(node);addToHead(node);}// 移除尾部节点(最久未使用)private Node removeTail() {Node node = tail.prev;removeNode(node);return node;}
}
实现说明
  • ​数据结构选择​

    • 双向链表:维护访问顺序
    • 哈希表:提供O(1)的键查找
  • ​哨兵节点​

    • 虚拟头节点(head)和尾节点(tail)简化边界操作
    • 避免空指针检查和特殊处理
  • ​关键操作

  • // 插入新节点
    head → newNode → nextNode↑          ↑head      nextNode// 移动节点到头部
    prevNode → node → nextNode  →  head → node → nextNode// 删除尾部节点
    tail.prev → tail  → 删除tail.prev
  • ​时间复杂度​

    • get(): O(1) (哈希查找 + 链表操作)
    • put(): O(1) (哈希操作 + 链表操作)

 使用示例

public class Main {public static void main(String[] args) {LRUCache<Integer, String> cache = new LRUCache<>(2);cache.put(1, "Apple");cache.put(2, "Banana");System.out.println(cache.get(1)); // 返回 "Apple"cache.put(3, "Cherry");  // 淘汰 key=2System.out.println(cache.get(2)); // 返回 nullcache.put(4, "Durian");  // 淘汰 key=1System.out.println(cache.get(1)); // 返回 nullSystem.out.println(cache.get(3)); // 返回 "Cherry"System.out.println(cache.get(4)); // 返回 "Durian"}
}
处理流程示意图
初始状态: 
head ↔ tail插入A: 
head → A ↔ tail插入B:
head → B ↔ A ↔ tail访问A:
head → A ↔ B ↔ tail插入C (容量满时):
1. 移除尾部B
2. head → C ↔ A ↔ tail
实际应用场景
  1. 数据库查询缓存
  2. 页面置换算法
  3. Redis内存管理
  4. 浏览器缓存管理
  5. CPU缓存系统
附:为什么要用双向列表+哈希表组合

LRU算法需要同时满足两个关键需求:

  1. ​快速查找​​(O(1)时间复杂度)
  2. ​快速维护访问顺序​​(O(1)移动/删除)
单数据结构无法同时满足:
数据结构查找效率维护顺序效率问题
数组O(1)随机访问O(n)移动元素移动元素成本高
单向链表O(n)查找O(1)移动节点查找效率低下
哈希表O(1)查找无法维护顺序无序存储
双向链表O(n)查找O(1)移动/删除查找效率低下
组合方案的优势:
  1. ​哈希表​​:提供O(1)的键值查找能力
  2. ​双向链表​​:提供O(1)的节点操作能力
    • 快速移动节点到头部(最近使用)
    • 快速删除尾部节点(淘汰最久未使用)
关键操作分析(O(1)时间复杂度):
1. 访问元素(get)

2. 插入元素(put)

为什么必须用双向链表?(单向链表的问题)

假设使用单向链表:

  • ​删除节点需要O(n)时间​​:

    • 需要遍历找到前驱节点才能删除
    • 伪代码:
      // 删除node需要的前驱节点
      Node prev = head;
      while(prev.next != node) {prev = prev.next; // O(n)遍历
      }
      prev.next = node.next;

  • ​无法直接操作尾部节点​​:

    • 删除LRU项时,无法快速访问前驱节点
关键实现细节解析
1. 节点删除(双向链表O(1))
private void removeNode(Node node) {node.prev.next = node.next; // 前驱指向后继node.next.prev = node.prev; // 后继指向前驱
}

2. 节点插入头部(双向链表O(1))
private void addToHead(Node node) {// 新节点连接原首节点node.next = head.next;node.prev = head;// 原首节点连接新节点head.next.prev = node;head.next = node;
}

设计决策总结
需求解决方案
O(1)查找哈希表存储键到节点的映射
O(1)插入新元素哈希表插入 + 链表头部插入
O(1)移动元素到前端双向链表节点删除/插入
O(1)删除最久未使用双向链表尾部指针 + 哈希表同步删除
避免边界条件虚拟头/尾节点(哨兵节点)

这种经典的组合实现了时间复杂度与空间效率的最佳平衡(O(1)操作 + O(n)空间),是现代系统中实现LRU缓存的黄金标准。Java的LinkedHashMap也是基于相同的原理实现的。

 

http://www.xdnf.cn/news/913879.html

相关文章:

  • 告别数据泥沼,拥抱智能中枢:King’s四位一体重塑科研生产力
  • 负载均衡器 LB》》
  • ABB输入/输出系统- S800输入/输出AI830A
  • 场景题-3
  • 【Linux】sed 命令详解及使用样例:流式文本编辑器
  • 【网页端数字人开发】基于模型SAiD实现嘴型同步
  • 三模冗余设计
  • 书籍推荐 --- 《筚路维艰:中国经济社会主义路径的五次选择》
  • 瑞它鲁肽 Retatrutide
  • Delphi 实现远程连接 Access 数据库的指南
  • 为什么HDI叠孔比错孔设计难生产
  • 调试时两个can盒子互连实现在一台电脑上自发自收的接线
  • Pytorch安装后 如何快速查看经典的网络模型.py文件(例如Alexnet,VGG)(已解决)
  • WiFi通信应用开发【保姆级】+配置ESP8266芯片的WiFi station和soft-AP + station工作模式!!!
  • 算力时代的四大引擎:CPU、GPU、NPU、DPU 深度解析
  • Vue3 + threeJs 定义六种banner轮播图切换动画效果:百叶窗、手风琴、拼图、渐变、菱形波次、圆形扩展
  • 如何利用 Redis 实现跨多个无状态服务实例的会话共享?
  • 讲解:Java I/O 流体系,并举例每个类的使用
  • 【YOLOs-CPP-图像分类部署】05-OpenVino加速
  • URL 带有 /../ 导致可以访问其他目录--路径穿越问题
  • SON.stringify()和JSON.parse()之间的转换
  • 优化电脑的磁盘和驱动器提高电脑性能和延长硬盘寿命?
  • Unity3D仿星露谷物语开发60之定制角色其他部位
  • Jpackage
  • 信号电压高,传输稳定性变强,但是传输速率下降?
  • Window Server 2019--11 虚拟专用网络
  • 软件测试python学习
  • 第十届电子技术和信息科学国际学术会议(ICETIS 2025)
  • 如何选择正确的团队交互模式:协作、服务还是促进?
  • 【普及+/提高】洛谷P2114 ——[NOI2014] 起床困难综合症