一,简介
LRU(Least Recently Used),最近最少使用算法,从名字上可能不太好理解,我是这样记的:LRU算法,淘汰最近一段时间内,最久没有使用过的数据。
本文使用HashMap和双向链表来实现LRU算法,原理如下图所示:
其中:
1.双向链表的主要功能是维护Node节点的顺序;
2.HashMap的主要功能是存储K-V缓存项,另外V为Node类型,也就是能通过key快速找到Node节点(快速定位到该Node节点在双链表中的位置,而不用遍历双链表来找该Node节点);
比如我要删除key为100的缓存项,那么根据HashMap的key快速找到100对应的node节点,然后在双向链表中将节点进行删除(修改前后Node的指针即可)。
当然可以将双向链表替换为单链表(也能保存顺序),但是这样会有问题:
1.每次定位到要删除的node后,都要从头开始再遍历一次链表,找到要删除的节点的前一个节点,然后修改指针进行删除节点操作;
2.每次查询到key对应的value后,需要将对应的Node移动到第一个位置(表示最近访问),那么有需要遍历一遍链表,然后在修改指针将节点进行移动;
鉴于以上原因,所以不考虑使用单链表。
二,实现(CPP实现)
#include<unordered_map>
using namespace std;
class LRUCache {
//LRU核心思想,维护一个大小为n的缓存,内部数据按照使用时间由近到远排列,当插入数据时,超过规定大小,则删除最后的元素。任何操作(访问,修改)都会让这个数据位置放在第一位
public:
//定义双链表
struct Node{
//双向链表使用的目的是便于访问链表的头尾部分,无需遍历
int key,value;
Node* left ,*right;
Node(int _key,int _value): key(_key),value(_value),left(NULL),right(NULL){}
}*L,*R;//双链表的最左和最右节点,不存贮值。
int n;
unordered_map<int,Node*>hash; //哈希表主要负责更快的找到指定节点,无需遍历双向链表,使缓存提速
void remove(Node* p)
{
//删除指定节点函数
p->right->left = p->left; //指定节点的下一个节点的前指针指向前前节点
p->left->right = p->right; //指定节点的前一个节点的后指针指向后后节点
}
void insert(Node *p)
{
//将指定节点插入至双向链表头(l,r维护双向链表的头尾指针)
p->right = L->right; //此节点的后指针指向头指针的后一节点
p->left = L; //此节点的左指针指向头节点
L->right->left = p; //头指针后一节点的前指针指向此节点
L->right = p; //头指针的后指针指向此节点
}
LRUCache(int capacity) {
//初始化LRU缓存
n = capacity;
L = new Node(-1,-1),R = new Node(-1,-1);
L->right = R;
R->left = L;
}
int get(int key) {
if(hash.count(key) == 0) return -1; //不存在关键字 key
auto p = hash[key]; //从哈希表取出该节点
remove(p); //移除该节点
insert(p);//将当前节点放在双链表的第一位
return p->value;
}
void put(int key, int value) {
if(hash.count(key)) //如果key存在,则修改对应的value
{
auto p = hash[key]; //从哈希表取出该节点
p->value = value; //修改
remove(p); //移除该节点
insert(p);//将当前节点放在双链表的第一位
}
else
{
if(hash.size() == n) //如果缓存已满,则删除双链表最右侧的节点
{
auto p = R->left; //获取最后节点
remove(p); //移除此节点
hash.erase(p->key); //更新哈希表
delete p; //释放内存
}
//否则,插入(key, value)
auto p = new Node(key,value);
hash[key] = p;
insert(p); //将当前节点放在双链表的第一位
}
}
};