题目
来源:力扣(LeetCode)
链接:LRU缓存机制
最近最久未使用算法
在操作系统的虚拟内存中,提供了比实际内存空间大的虚拟存储。而这一技术是通过请求页表(段表)机制、地址变换机构、置换算法以及其他硬件和软件基础实现的。
这一功能可以实现进程的非连续存储,即分配给一个进程的空间可以不是连续的,这就使得当系统只需要执行这一进程中的某一模块的指令时,可以暂时将这一模块加载进内存中,从而提供了运行效率并且节省了空间(没有内部碎片)。
如果当系统分配给某个进程的内存空间不足时,这个时候就需要将这个进程中加载入内存中的某一页(段)移出内存。
最近最久未使用算法就是这个进程在内存中的页面挑选最近最久未使用的一页移出内存。如分配给进程P的空间为2个单位,而访问页面序列为1 ,2 ,1,3。当访问页面3时,3不在内存中,系统产生缺页中断。因为最后访问的页面是1号页面,所以需要从从内存中的1,2页面中挑选2换出内存。
思路
Tips:
- map为关联容器 ,默认按照key排序,不能实现按照value来排序,若要按照value来排序则需要转为数值容器,进行排序。
- 如果使用某种种容器 既可以进行hash映射,又可以实现优先队列的功能,其中优先队列结点按访问时间进行排序,可以实现。但优先队列每访问一个结点,就需要更新结点域中的时间再排序一次,复杂度过高。
仔细想优化,如果使用双向链队列则可以使得移动的复杂度为o(1),且省去排序的时间,直接删除队头,插入队尾,可以实现功能。
综上需要实现以下两种结构:
- 用 map关联<key,Node>
- 使用一种特殊队列,每次被访问则将该该节点移动至队尾,插入只能在队尾插,删除只能在队头删
代码
struct Node{
int key;
int val;
Node *front;
Node *next;
Node(int k,int v){
key=k;
val=v;
front=NULL;
next=NULL;
}
};
class QueueList{
private:
Node * rear;//尾指针
Node * front;//头指针
int size;//队列实际大小
public:
QueueList(){
size=0;
front=new Node(-1,-1);
rear=front;
}
int getSize(){
return size;
}
int getFrontKey(){
return front->next->key;
}
void removeQueue(){//删除队头
if(rear==front) return;//空队列
Node*tmp=front->next;
if(tmp->next){
tmp->next->front=front;
front->next=tmp->next;
delete tmp;
}else{//删除的为最后一个元素
delete tmp;
front->next=NULL;
rear=front;//防止rear指针悬空
}
this->size--;
}
void insertQueue(Node *&p){//在队列队尾插入结点p
rear->next=p;
p->front=rear;
rear=p;
rear->next=NULL;
this->size++;
}
void moveToLast(Node*&p){
if(p->next){//不在队尾
p->next->front=p->front; //摘下p
p->front->next=p->next;
p->front=rear;
rear->next=p;
rear=rear->next;
rear->next=NULL;
}
}
};
class LRUCache {
private:
unordered_map<int,Node*> mp;
int capacity;//队列最大容量
QueueList* queue;
public:
LRUCache(int capacity) {
this->capacity=capacity;
queue=new QueueList();
}
int get(int key) {
if(mp.find(key)!=mp.end()){
queue->moveToLast(mp[key]);
return mp[key]->val;
}else {
return -1;
}
}
void put(int key, int value) {
if(mp.find(key)==mp.end()){
//不存在key 插入<key,value>
Node *p= new Node(key,value);
if(queue->getSize()==capacity){
int fisrtKey=queue->getFrontKey();
mp.erase(fisrtKey);//从键值对中删除队头对应的键值对
queue->removeQueue();
}
queue->insertQueue(p);
mp[key]=p;
}else{//存在key 更新value值并将其放至队尾
mp[key]->val=value;
queue->moveToLast(mp[key]);
}
}
};
/**
* Your LRUCache object will be instantiated and called as such:
* LRUCache* obj = new LRUCache(capacity);
* int param_1 = obj->get(key);
* obj->put(key,value);
*/