LRU算法O(1)的复杂度实现:
如果不是不要求时间复杂度,可以采用链表加时间戳的形式实现
O(1)的时间复杂度,单单采用双向链表的数据结构是不够的,虽然删除和添加的时间复杂度为O(1)的,但是我们想要定位到要删除的元素,还是需要从头开始依次遍历才可以,如何O(1)定位到想要的节点,可以采用散列表,所以最终我们采用散列表+双向链表的形式实现。
当然如果不要求手撕的话,可以直接使用LinkedHashMap。
我这里直接手撕了:
public class LRUCache {
private int capacity;
private HashMap<Integer,DLikedNode> map = new HashMap<>();
private DLikedNode head;
private DLikedNode tail;
class DLikedNode{
int key;
int value;
DLikedNode pre;
DLikedNode next;
}
public LRUCache(int capacity) {
this.capacity = capacity;
head = new DLikedNode();
tail = new DLikedNode();
head.next = tail;
tail.pre = head;
}
private void addNode(DLikedNode node){
map.put(node.key,node);
tail.pre.next = node;
node.pre = tail.pre;
node.next = tail;
tail.pre = node;
}
private void removeNode(DLikedNode node){
map.remove(node.key);
node.pre.next = node.next;
node.next.pre = node.pre;
}
public int get(int key) {
DLikedNode temp = map.get(key);
if(null == temp){
return -1;
}else{
//将temp移到尾部
this.removeNode(temp);
this.addNode(temp);
return temp.value;
}
}
public void put(int key, int value) {
// 先判断是否是已经纯在的了
DLikedNode temp = map.get(key);
if(temp !=null) {
temp.value = value;
this.removeNode(temp);
this.addNode(temp);
return;
}
temp = new DLikedNode();
temp.key = key;
temp.value = value;
if(capacity<=0){
//删除头部数据,在尾部新增一个node
this.removeNode(head.next);
}
this.addNode(temp);
this.capacity--;
}
}
此题,手写第一版,感觉并不是很美,过两天有时间,看看国外大神的代码,再优化一下。
大致思路就是head节点是最不常用的,如果超出存储数据个数,先删除head,再在tail前加入新节点,对于get的节点,删除当前位置,在tail前新增一个节点。
对应leetcode,146题,很多缓存中,都采用这个算法,另外在linux系统中,对于进程的内存管理,采用虚拟内存,以及分页加载,其中分页加载,也用到了LRU算法。