最近最久未使用LRU算法实现!
发表于更新于
计算机基础算法最近最久未使用LRU算法实现!
月伴飞鱼
LRU全称是Least Recently Used,即最近最久未使用的意思。
如果一个数据在最近一段时间没有被使用,将来被使用的机会也比较小。
实现最不经常使用(LRU)缓存的数据结构:应该支持以下操作:get 和 put。
get(key):
- 如果键存在于缓存中,则获取键的值(总是正数),否则返回 -1。
put(key, value):
- 如果键不存在,请设置或插入值。
- 当缓存达到其容量时,它应该在插入新项目之前,使最不经常使用的项目无效。
解决思路
单链表对于 put 操作,会出现以下几种情况:
如果要 put(key,value) 已经存在于链表之中(根据key来判断):
- 那么需要把链表中旧的数据删除,然后把新的数据插入到链表的头部。
如果要 put(key,value) 的数据没有存在于链表之中:
- 需要判断缓存区是否已满,如果满的话,则把链表尾部的节点删除,之后把新的数据插入到链表头部。
如果没有满的话,直接把数据插入链表头部即可。
单链表对于 get 操作,则会出现以下情况:
如果要 get(key) 的数据存在于链表中:
- 则把 value 返回,并且把该节点删除,删除之后把它插入到链表的头部(表示最新用到了该节点数据)。
如果要 get(key) 的数据不存在于链表之中:
时间、空间复杂度分析
对于这种方法,put 和 get 都需要遍历链表查找数据是否存在,所以时间复杂度为 O(n),空间复杂度为 O(1)。
哈希表+单链表
可以考虑采用空间换时间的方式来加快 get 的操作。
用一个额外哈希表(例如HashMap)来存放 key-value。
这样,get 操作就可以在 O(1) 的时间内寻找到目标节点。
还需要删除该元素,并且把该元素插入到链表头部。
删除一个元素,需要定位到这个元素的前驱的,定位到这个元素的前驱,需要 O(n) 时间复杂度。
双向链表+哈希表
采用这两种数据结构的组合,get 操作在 O(1) 时间复杂度。
由于 put 操作要删除的节点一般是尾部节点。
所以可以用一个变量 tai 时刻记录尾部节点的位置,这样,put 操作也可以在 O(1) 时间内完成。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
| class LRUNode{ String key; Object value; LRUNode next; LRUNode pre;
public LRUNode(String key, Object value) { this.key = key; this.value = value; } }
public class LRUCache { Map<String, LRUNode> map = new HashMap<>(); LRUNode head; LRUNode tail; int capacity;
public RLUCache(int capacity) { this.capacity = capacity; }
public void put(String key, Object value) { if (head == null) { head = new LRUNode(key, value); tail = head; map.put(key, head); } LRUNode node = map.get(key); if (node != null) { node.value = value; removeAndInsert(node); } else { LRUNode tmp = new LRUNode(key, value); if (map.size() >= capacity) { map.remove(tail); tail = tail.pre; tail.next = null; } map.put(key, tmp); tmp.next = head; head.pre = tmp; head = tmp; } }
public Object get(String key) { LRUNode node = map.get(key); if (node != null) { removeAndInsert(node); return node.value; } return null; } private void removeAndInsert(LRUNode node) { if (node == head) { return; } else if (node == tail) { tail = node.pre; tail.next = null; } else { node.pre.next = node.next; node.next.pre = node.pre; } node.next = head; node.pre = null; head.pre = node; head = node; } }
|
带有过期时间的LRU实现
每一个节点放一个过期时间,只要到了这个时间就直接删除即可。
- 添加一个过期时间队列,和一个过期清除的线程。
- 清除的时候使用
while(true)每次判断队列队首是否过期,然后判断是否返回和清除。