一、概念
计算机的缓存容量有限,如果缓存满了就要删除一些内容,给新内容腾位置。但问题是,删除哪些内容呢?我们肯定希望删掉哪些没什么用的缓存,而把有用的数据继续留在缓存里,方便之后继续使用。那么,什么样的数据,我们判定为「有用的」的数据呢?
LRU 缓存淘汰算法就是一种常用策略。LRU 的全称是 Least Recently Used,即最近最少被使用,也就是说我们认为最近使用过的数据应该是是「有用的」,很久都没用过的数据应该是无用的,内存满了就优先删那些很久没用过的数据。
二、算法描述
获取数据 get 和 写入数据 put 。
获取数据 get(key) - 如果密钥 (key) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1。
写入数据 put(key, value) - 如果密钥不存在,则写入其数据值。当缓存容量达到上限时,它应该在写入新数据之前删除最近最少使用的数据值,从而为新的数据值留出空间。
三、算法思想
要让 put 和 get 方法的时间复杂度为 O(1)O(1),我们可以总结出 cache 这个数据结构必要的条件:查找快,插入快,删除快,有顺序之分。
因为显然 cache 必须有顺序之分,以区分最近使用的和久未使用的数据;而且我们要在 cache 中查找键是否已存在;如果容量满了要删除最后一个数据;每次访问还要把数据插入到队头。
那么,什么数据结构同时符合上述条件呢?哈希表查找快,但是数据无固定顺序;链表有顺序之分,插入删除快,但是查找慢。所以结合一下,形成一种新的数据结构:哈希链表。
LRU 缓存算法的核心数据结构就是哈希链表,双向链表和哈希表的结合体。
四、代码及具体思路
#include <list>
#include <iterator>
#include <unordered_map>
#include <map>
using namespace std;
class LRUCache {
public:
int cap = 0;
LRUCache(int capacity)
{
this->cap = capacity;
}
int get(int key)
{
auto iter = map.find(key);
//没有在map中找到
if (iter == map.end())
return -1;
else
{
int value = iter->second->second;
cache.push_front(*(iter->second));
cache.erase(iter->second);
map[key] = cache.begin();
return value;
}
}
void put(int key, int value)
{
//先判断key是否存在
auto iter = map.find(key);
//key不存在
if (iter == map.end())
{
//判断缓存容量是否已满
if (cache.size() == cap)
{
//容量满需要释放cache 和map
auto lst = cache.back();
map.erase(lst.first);
cache.pop_back();
}
//再插入
cache.push_front({ key,value });
map[key] = cache.begin();
}
//key已经存在
else
{
//先将缓存中原数据删除
cache.erase(iter->second);
//再加入
cache.push_front({ key,value });
map[key] = cache.begin();
}
}
protected:
list<pair<int, int>> cache;
unordered_map <int, list<pair<int, int>>::iterator> map;
};
void main()
{
LRUCache cache (2);
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // 返回 1
cache.put(3, 3); // 该操作会使得密钥 2 作废
cache.get(2); // 返回 -1 (未找到)
cache.put(4, 4); // 该操作会使得密钥 1 作废
cache.get(1); // 返回 -1 (未找到)
cache.get(3); // 返回 3
cache.get(4); // 返回 4
}
以上就是本篇文章的全部内容,如有不足,请多批评指正。