LRU 缓存算法的核心数据结构就是哈希链表,双向链表和哈希表的结合体。这个数据结构长这样:
其实很简单:哈希表(unordered_map)中的键值对为unordered_map<int, list<pair<int, int>>::iterator>
;双向链表(list)中的元素类型为list<pair<int, int>>
。
分析上面的操作过程,要让 put 和 get 方法的时间复杂度为O(1),我们可以总结出 cache 这个数据结构必要的条件:查找快,插入快,删除快,有顺序之分。
因为显然 cache 必须有顺序之分,以区分最近使用的和久未使用的数据;而且我们要在 cache 中查找键是否已存在;如果容量满了要删除最后一个数据;每次访问还要把数据插入到队头。
那么,什么数据结构同时符合上述条件呢?哈希表查找快,但是数据无固定顺序;链表有顺序之分,插入删除快,但是查找慢。所以结合一下,形成一种新的数据结构:哈希链表。
代码实现如下:(这个时候STL用起来就很舒服了)
class LRUCache {
private:
int capacity;
// 双端链表:装着 (key, value) 二元组
list<pair<int, int>>cache;
//哈希表中存着(key,key在list中的迭代器)
unordered_map<int, list<pair<int, int>>::iterator>mp;
public:
LRUCache(int capacity) {
this->capacity = capacity;
}
int get(int key) {
//unordered_map<int, list<pair<int, int>>::iterator>::iterator it = mp.find(key);
auto it = mp.find(key);
if (it == mp.end())// key 不存在
{
return -1;
}
// key 存在,把 (k, v) 换到队头
pair<int, int> temp = *mp[key];
cache.erase(mp[key]);
cache.push_front(temp);
// 更新哈希表
mp[key] = cache.begin();
return temp.second;
}
void put(int key, int value) {
//要先判断 key 是否已经存在
auto it = mp.find(key);
if (it == mp.end())//key不存在
{
//还要判断cache是否满了
if (cache.size() == this->capacity)//满了
{
//删除链表尾结点
// 注意cache 和 map 中的数据都要删除
pair<int,int>temp=cache.back();//尾元素的引用
cache.pop_back();
mp.erase(temp.first);//在map中删除键为temp.first的二元组
}
//开始put
cache.push_front(make_pair(key,value));
mp.insert(make_pair(key,cache.begin()));
}
else//key存在
{
//map中更新value,list中删除旧的二元组,并把新的二元组放到链表头
cache.erase(mp[key]);
cache.push_front(make_pair(key, value));
mp[key] = cache.begin();
}
}
};