LRU缓存策略
分析:为了保持cache的性能,使查找,插入,删除都有较高的性能,我们使用双向链表(std::list)和哈希表(std::unordered_map)作为cache的数据结构,因为:
- 双向链表插入删除效率高(单向链表插入和删除时,还要查找节点的前节点)
- 哈希表保存每个节点的地址,可以基本保证在O(1)时间内查找节点
具体实现细节:
- 越靠近链表头部,表示节点上次访问距离现在时间最短,尾部的节点表示最近访问最少
- 查询或者访问节点时,如果节点存在,把该节点交换到链表头部,同时更新hash表中该节点的地址
- 插入节点时,如果cache的size达到了上限,则删除尾部节点,同时要在hash表中删除对应的项。新节点都插入链表头部。
方法一:
class LRUCache{
public:
// @param capacity, an integer
LRUCache(int capacity) {
// write your code here
cap=capacity;
}
// @return an integer
int get(int key) {
// write your code here
if(m.find(key)==m.end())
return -1;
else
l.splice(l.begin(), l, m.find(key)->second);//将此元素在双链表中位置挪到顶部
return m.find(key)->second->second;
}
// @param key, an integer
// @param value, an integer
// @return nothing
void set(int key, int value) {
// write your code here
auto it = m.find(key);
if (it != m.end()) l.erase(it->second);//如果存在则删掉当前位置
l.push_front(make_pair(key, value));//在顶部添加
m[key] = l.begin();//更新哈希表中此元素在链表中的位置
if (m.size() > cap) {//缓存达到上限
int k = l.rbegin()->first;//获得最后元素的key
l.pop_back();
m.erase(k);
}
}
private:
int cap;//容器容量
list<pair<int,int>> l;//用双链表表示这个数据结构,pair两个分别表示key 和value
unordered_map<int,list<pair<int,int>>::iterator> m;//用哈希表辅助以便在O(1)时间找到目标项
};
方法二:
struct CacheNode
{
int key;
int value;
CacheNode(int k, int v):key(k), value(v){}
};
class LRUCache{
public:
LRUCache(int capacity) {
size = capacity;
}
int get(int key) {
if(cacheMap.find(key) == cacheMap.end())
return -1;
else
{
//把当前访问的节点移到链表头部,并且更新map中该节点的地址
cacheList.splice(cacheList.begin(), cacheList, cacheMap[key]);
cacheMap[key] = cacheList.begin();
return cacheMap[key]->value;
}
}
void set(int key, int value) {
if(cacheMap.find(key) == cacheMap.end())
{
if(cacheList.size() == size)
{//删除链表尾部节点(最少访问的节点)
cacheMap.erase(cacheList.back().key);
cacheList.pop_back();
}
//插入新节点到链表头部,并且更新map中增加该节点
cacheList.push_front(CacheNode(key, value));
cacheMap[key] = cacheList.begin();
}
else
{//更新节点的值,把当前访问的节点移到链表头部,并且更新map中该节点的地址
cacheMap[key]->value = value;
cacheList.splice(cacheList.begin(), cacheList, cacheMap[key]);
cacheMap[key] = cacheList.begin();
}
}
private:
list<CacheNode> cacheList;
unordered_map<int, list<CacheNode>::iterator>cacheMap;
int size;
};
方法三:自己实现一个双向链表
struct Node
{
Node(int _key,int _value){key = _key;value = _value;pre = next = NULL;}
int key;
int value;
Node*pre;
Node*next;
};
class LRUCache{
public:
// @param capacity, an integer
LRUCache(int capacity) {
// write your code here
this->capacity = capacity;
size = 0;
head = NULL;
tail = NULL;
}
// @return an integer
int get(int key) {
// write your code here
if(m.find(key)==m.end())
{
return -1;
}
else
{
Node* n = m[key];
int ret = n->value;
if(head!=n)
{
if(n==tail)
tail = tail->pre;
Node*pre = n->pre;
if(n->next!=NULL)
n->next->pre = pre;
pre->next = n->next;
n->next = head;
head->pre = n;
head = n;
}
return ret;
}
}
// @param key, an integer
// @param value, an integer
// @return nothing
void set(int key, int value) {
// write your code here
if(head==NULL)
{
head = new Node(key,value);
tail = head;
size++;
m[key] = head;
return;
}
if(m.find(key)==m.end())
{
Node*n = new Node(key,value);
n->next = head;
head->pre = n;
head = n;
m[key] = n;
size++;
}
else
{
Node*n = m[key];
if(head!=n)
{
if(n==tail)
tail = tail->pre;
Node*pre = n->pre;
pre->next = n->next;
if(n->next!=NULL)
n->next->pre = pre;
n->next = head;
head->pre = n;
head = n;
//m[key] = n; n本身的地址并不变,不需要重新记录
}
head->value = value; //这个一定需要,因为set可能会相同的KEY 而value不同
}
if(size>capacity)
{
Node*todel = tail;
int keyToDel = todel->key;
tail = tail->pre;
tail->next = NULL;
delete todel;
m.erase(m.find(keyToDel));
size--;
}
}
private:
int capacity;
int size;
Node*head;
Node*tail;
unordered_map<int,Node*> m;
};
参考资料:
http://www.cnblogs.com/grandyang/p/4587511.html
http://blog.csdn.net/wangyuquanliuli/article/details/47357805
http://www.cnblogs.com/TenosDoIt/p/3417157.html