1.题目描述
运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制 。
实现 LRU Cache 类:
- LRUCache(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存
- int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
- void put(int key, int value) 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字-值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。
2.实现思路
栈+map
or
双链表+map
get(key):直接获取map元素,并将被访问元素置于栈顶
put(key):元素入栈,若栈满,删除栈底元素;若更新元素,将元素置于栈顶,并更新map对应value
双链表模拟栈的优势:链头模拟栈底,链尾模拟栈顶,剪接节点置于栈顶的操作只需要O(1)复杂度
3.代码
class LRUCache {
private:
int capasize_;
list<pair<int,int>>cache;//双向链表
unordered_map<int,list<pair<int,int>>::iterator> mp;//哈希表
public:
//构造函数
LRUCache(int capacity) {
capasize_=capacity;
}
int get(int key) {
const auto it=mp.find(key);
//判断key是否存在哈希表中
if(it==mp.cend())
return -1;//不存在返回-1
//如果存在则将双向链表中含关键字key的节点移至头部
cache.splice(cache.begin(),cache,it->second);
//返回value
return it->second->second;
}
void put(int key, int value) {
const auto it=mp.find(key);
//判断key是否存在哈希表中,存在则改变value,然后将双向链表中含关键字key的节点移至头部
if(it!=mp.cend())
{
it->second->second=value;
cache.splice(cache.begin(),cache,it->second);
return;//记得return
}
//如果不存在则要判断此时双向链表容量是否已满
//若满了则要去除双向链表中的尾节点以及哈希表中key关键字位置
if(cache.size()==capasize_)
{
const auto&node=cache.back();
mp.erase(node.first);
cache.pop_back();
}
//新建一个节点将其插入到链表头部
cache.emplace_front(key,value);
//让哈希表中key关键字指向链表新的头节点
mp[key]=cache.begin();
}
};
4.STL的list学习
注意:STL中的list是双向链表。
list::splice实现list拼接的功能。将源list的内容部分或全部元素删除,拼插入到目的list。
函数有以下三种声明:
一:void splice ( iterator position, list<T,Allocator>& x );
二:void splice ( iterator position, list<T,Allocator>& x, iterator it );
三:void splice ( iterator position, list<T,Allocator>& x, iterator first, iterator last );
解释:
position 是要操作的list对象的迭代器
list&x 被剪的对象
对于一:会在position后把list&x所有的元素到剪接到要操作的list对象
对于二:只会把it的值剪接到要操作的list对象中
对于三:把first 到 last 剪接到要操作的list对象中
5.拓展:实现LFU(最不经常使用缓存算法)
扫描二维码关注公众号,回复:
13126250 查看本文章

请你为 最不经常使用(LFU)缓存算法设计并实现数据结构。
实现 LFUCache 类:
- LFUCache(int capacity) - 用数据结构的容量 capacity 初始化对象
- int get(int key) - 如果键存在于缓存中,则获取键的值,否则返回 -1。
- void put(int key, int value) - 如果键已存在,则变更其值;如果键不存在,请插入键值对。当缓存达到其容量时,则应该在插入新项之前,使最不经常使用的项无效。在此问题中,当存在平局(即两个或更多个键具有相同使用频率)时,应该去除 最久未使用 的键。
class LFUCache {
private:
// 双向链表节点
struct DLinkNode {
int key, val, freq;
// 节点构造函数
DLinkNode(int key, int val, int freq) : key(key), val(val), freq(freq) {
}
};
// 最小频率,表示最近最少使用的链表
int minFreq;
// 缓存的容量
int capacity;
// 哈希表:存储(key, 链表节点在链表中的位置)
std::unordered_map<int, std::list<DLinkNode>::iterator> key_table;
// 哈希表:存储(key, val, freq)
std::unordered_map<int, std::list<DLinkNode>> freq_table;
public:
LFUCache(int capacity) {
this->minFreq = 0;
this->capacity = capacity;
key_table.clear();
freq_table.clear();
}
int get(int key) {
if (capacity == 0) {
return -1;
}
// key_table中是否存在该key
auto iter = key_table.find(key);
// 如果不存在,返回-1
if (iter == key_table.end()) {
return -1;
}
// 如果存在该key,
// node为iter指向的节点,即key对应的节点
auto node = iter->second;
// 该节点的val和freq值
int val = node->val;
int freq = node->freq;
// 因为get(key)属于操作,所以该节点频率加1
// 将该节点从哈希表原链表freq_table[freq]中删除
freq_table[freq].erase(node);
// 如果删除该节点后,freq_table[freq]链表为空
if (freq_table[freq].empty()) {
// 则从freq_table中删除该频率freq对应的双向链表
freq_table.erase(freq);
// 如果最小频率minFreq等于freq,因为freq使用后频率加1,
// 所以minFreq最小频率也要更新为加1
if (minFreq == freq) {
minFreq += 1;
}
}
// 头部表示最近使用,尾部表示最近最久未使用
// 将该节点添加到链表freq_table[freq+1]的头部,并将该节点的频率更新
freq_table[freq + 1].push_front(DLinkNode(key, val, freq + 1));
// 同时更新key_table中key所对应节点在新链表freq_table[freq+1]的头部位置
key_table[key] = freq_table[freq + 1].begin();
return val;
}
void put(int key, int value) {
if (capacity == 0) {
return;
}
// key_table中是否存在该key
auto iter = key_table.find(key);
// 如果不存在该key对应的节点,就将该节点插入到频率对应的双向链表头部
if (iter == key_table.end()) {
// 首先判断当前缓存大小是否达到容量
if (key_table.size() == capacity) {
// 如果当前缓存大小已经达到容量,则需要先删除最近最少使用的节点
// 在哈希表中找到最近最少使用的双向链表freq_table[minFreq]
// 因为最近加入的节点都在头部,所以最近最少使用的是尾部节点
auto it = freq_table[minFreq].back();
// 删除key_table中最近最少使用的节点
key_table.erase(it.key);
// 从双向链表freq_table[minFreq]中删除尾节点
freq_table[minFreq].pop_back();
// 判断此时频率最小的双向链表是否为空
if (freq_table[minFreq].empty()) {
// 如果为空则删除频率最小的双向链表
freq_table.erase(minFreq);
}
}
// 因为新插入节点的频率为1,所有将其插入到freq_table[1]链表中
freq_table[1].push_front(DLinkNode(key, value, 1));
// 同时更新key_table中key所对应的节点在频率链表中相应的位置
key_table[key] = freq_table[1].begin();
// 更新最小频率为1
minFreq = 1;
} else {
// 如果存在该key,
// node为iter指向的节点,即key对应的节点
auto node = iter->second;
// 获得该节点的freq值
int freq = node->freq;
// 因为该节点因使用后频率加1,所以从原频率链表中删除该节点
freq_table[freq].erase(node);
// 判断节点原频率链表是否为空
if (freq_table[freq].empty()) {
// 如果为空,则从哈希表中删除该频率链表
freq_table.erase(freq);
// 如果最小频率等于节点原频率,则将最小频率更新加1
if (minFreq == freq) {
minFreq += 1;
}
}
// 将该节点频率加1后添加到频率加1的双向链表的头部
freq_table[freq + 1].push_front(DLinkNode(key, value, freq + 1));
// 同时更新key所对应的节点在相应频率链表中的头部位置
key_table[key] = freq_table[freq + 1].begin();
}
}
};