LRU 缓存
个人思路
采用C++的容器,没有手撕双向链表
题意
实现LRU的初始化,读取,写入,分别对应LRUCache()、get()、put()
用到的理论和技术
- 双向链表list的插入、删除、访问、迭代器、auto关键字
- map,插入、删除、访问
- 类的初始化
- LRU机制
思路
LRU(Least Recently Used),最近最少使用算法,是页面置换算法的一种
也叫最近最久未使用算法
LRUCache():初始化内存的大小
get():获取内存中的数据,若不存在返回-1
put():向内存写入数据。
- 若数据存在,则更新并访问当前数据,表示当前使用过当前数据;
- 如果不存在
- 内存没有超限,则写入当前数据,表示当前使用过当前数据。
- 内存超限,删去最近最久未使用的数据,写入当前数据,表示当前使用过当前数据。
数据结构:
下文规定,哈希表map统称键值;输入数据统称key,val
- pair<int,int>:输入数据是key,val的形式,数据形式:key,val
- 双向链表List<pair<int, int> >,双向链表
- map<int, pair<int, int>::iterator>,mp[key]:map的键 == 数据的key,map的值 == 数据 key,val在双向链表中的迭代器
Q1:为什么要用链表
A:由于算法考虑到访问的顺序,因此需要使用链表
Q2:为什么要用双向量表
A:链表头部表示最近使用过,链表尾部表示最近未使用过,因此涉及到头部的插入和尾部的插入与删除
Q3:为什么要用map
A:要保证常规时间内插入删除
Q4:为什么map的值是key-val对所在双向链表中的迭代器
A:便于list.erase(iter),在任意位置删除
Q5:既然哈希表中已经存了 数据的key,为什么链表中还要存数据key-val对呢,只存val不行么?
A:当内存满了,双向链表末尾数据被删除,对应的哈希表中的旧数据也要删除,然而哈希表的删除是根据哈希表的值进行的,链表中只存数据的val,是无法找到对应数据的key
注意
- list的函数,begin()和end()表示的不是元素,而是迭代器
- 元素是通过front()和back()访问的
- end()表示的不是容器最后一个元素的迭代器,而是容器末端下一位置
- auto访问map时,这里的auto推导出来的类型并不是迭代器类型,而是键值对(pair)类型
//因此get()不能写成 //key存在,返回key的val,并把键值对提到链表头部 auto key_value = *hash[key];//这里的auto推导出来的类型并不是迭代器类型,而是键值对(pair)类型,不能省略 cache.erase(hash[key]); //cache删除旧数据 cache.push_front(*hash[key]); //cache把键值对提到链表头部,此处不能用*hash[key]替换auto,此时catche需要的是pair,auto类型转换结果是pair,但*hash[key]结果不是pair hash[key] = cache.begin(); return cache.front().second;
个人思路代码
伪代码
// key 映射到 Node(key, val)
HashMap<Integer, Node> map;
// Node(k1, v1) <-> Node(k2, v2)...
DoubleList cache;
int get(int key) {
if (key 不存在) {
return -1;
} else {
将数据 (key, val) 提到开头;
return val;
}
}
void put(int key, int val) {
Node x = new Node(key, val);
if (key 已存在) {
把旧的数据删除;
将新节点 x 插入到开头;
} else {
if (cache 已满) {
删除链表的最后一个数据腾位置;
删除 map 中映射到该数据的键;
}
将新节点 x 插入到开头;
map 中新建 key 对新节点 x 的映射;
}
}
class LRUCache {
private:
int cap;
list<pair<int, int> > cache; //双向链表,保证头尾节点在常规时间内删除、插入
//hash的键存储 数据的key,hash的值存储 数据key-val键值对在双向链表中的迭代器
unordered_map<int, list<pair<int, int> >::iterator> hash;
public:
LRUCache(int capacity) {
cap = capacity;
}
//获取缓存中的关键字
int get(int key) {
if(hash.count(key) == 0){
//key不存在,返回-1
return -1;
}
else{
//key存在,返回key的val,并把键值对提到链表头部
auto key_value = *hash[key];//这里的auto推导出来的类型并不是迭代器类型,而是键值对(pair)类型,不能省略
cache.erase(hash[key]); //cache删除旧数据
cache.push_front(key_value); //cache把键值对提到链表头部
hash[key] = cache.begin();
return cache.front().second;
}
}
//插入缓存
void put(int key, int value) {
if(hash.count(key) != 0){
//key值已存在,把旧数据删除,新数据插入到双向链表头部
cache.erase(hash[key]);//删去cache中的旧数据
}
else{
//key值不存在
if(cache.size() >= cap){
//内存超限,删除双向链表末尾数据,删除hash中的旧数据,新数据插入到双向链表头部
hash.erase(cache.back().first);//hash删除末尾数据
// cache.erase(cache.end());//cache删除末尾数据,末尾数据的地址并非cache.end(),如for(iter != mp.end())
cache.pop_back();
}
}
//新数据插入到双向链表头部
cache.push_front({key, value});
hash[key] = cache.begin();
}
};
/**
* 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);
*/
参考
作者:labuladong
链接:https://leetcode-cn.com/problems/lru-cache/solution/lru-ce-lue-xiang-jie-he-shi-xian-by-labuladong/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。