概念:
LFU(least frequently used (LFU) page-replacement algorithm)。即最不经常使用页置换算法,要求在页置换时置换引用计数最小的页,因为经常使用的页应该有一个较大的引用次数。但是有些页在开始时使用次数很多,但以后就不再使用,这类页将会长时间留在内存中,因此可以将引用计数寄存器定时右移一位,形成指数衰减的平均使用次数。
题目要求:原题
请你为 最不经常使用(LFU)缓存算法设计并实现数据结构。
实现 LFUCache 类:
1.LFUCache(int capacity) - 用数据结构的容量 capacity 初始化对象
2.int get(int key) - 如果键 key 存在于缓存中,则获取键的值,否则返回 -1 。
3.void put(int key, int value) - 如果键 key 已存在,则变更其值;如果键不存在,请插入键值对。当缓存达到其容量 capacity 时,则应该在插入新项之前,移除最不经常使用的项。在此问题中,当存在平局(即两个或更多个键具有相同使用频率)时,应该去除 最近最久未使用 的键。
4.为了确定最不常使用的键,可以为缓存中的每个键维护一个 使用计数器 。使用计数最小的键是最久未使用的键。
当一个键首次插入到缓存中时,它的使用计数器被设置为 1 (由于 put 操作)。对缓存中的键执行 get 或 put 操作,使用计数器的值将会递增。
说明:
1.官方题解并未考虑页面长时间留存在内存中但是不再被使用的情况,因此并不需要右移,对于做题来说完全足够;
2.这里我添加了默认构造函数,所以可以使用hash_map内部重载的[ ]运算符,否则是只能通过迭代器访问并修改hash_map中的Node的(就像官方题解一那样)
3.整体思路就是分为get() put() 两部分,利用set中红黑树的性质对node进行默认排序,利用hash_map存储<key,Node>设置一个时间戳,当freq即访问频率一样时就淘汰最早使用过的内存页,重点在于Node结构体中默认构造函数的必要性和重载<运算符的必要性
4.这道题不能只用一个map搞定,因为map的话只能使用map<int,Node>,那么就不能按照Node排序,而是按照key排序了
代码:
#include<iostream>
#include<set>
#include<unordered_map>
using namespace std;
constexpr int default_capacity = 50; // 默认缓存容量
struct Node {
int freq, time, key, val;
Node():freq(1), key(0), val(0), time(0) {}; // 这里注意,虽然代码中默认构造函数根本没用到,但是hash_map在重载[]运算符的时候要用到T(),也就是Node()
Node(int _key, int _val, int _time) :freq(1), key(_key), val(_val), time(_time){};
bool operator < (const Node& rhs) const { // 重载 < 运算符,设置成常函数
return freq == rhs.freq ? time < rhs.time : freq < rhs.freq;
}
};
class LFU {
private:
unordered_map<int, Node>key_table;
set<Node>freq_set; // 利用set红黑树的性质,通过重载<运算符对Node在freq_set内部进行排序
int time; // 时间戳
int capacity; // 缓存容量
public:
LFU():capacity(default_capacity), time(0) {}
LFU(int _capacity):capacity(_capacity), time(0) {}
~LFU() {}
void put(int key, int value) {
if (key_table.find(key) != key_table.end()) {
Node node = key_table[key];
freq_set.erase(node);
++node.freq; // 更新访问频率
node.val = value; // 更新值
node.time = ++time; // 更新时间戳
key_table[key] = node; // 由于node是临时对象,所以更新完node后要重新插入set和map
freq_set.insert(node);
}
else {
if (key_table.size() == capacity) { // 缓存满了
auto node = freq_set.begin(); // 头指针,指向最少访问或者最早添加的缓存
key_table.erase(node->key);
freq_set.erase(node);
}
Node node = Node(key, value, ++time);
freq_set.insert(node);
key_table[key] = node;
}
}
int get(int key) {
if (key_table.find(key) != key_table.end()) {
Node node = key_table[key];
freq_set.erase(node);
++node.freq;
node.time = ++time;
freq_set.insert(node);
key_table[key] = node;
return node.val;
}
return -1;
}
void print() { // 打印检查
for (auto it = freq_set.begin(); it != freq_set.end(); ++it) {
cout << it->key << " " << it->time << " " << it->freq << endl;
}
}
};
int main() {
LFU lfu(2);
lfu.put(1,1);
lfu.put(2, 1);
lfu.print();
lfu.get(1);
lfu.print();
lfu.put(3,1);
lfu.print();
lfu.put(3, 2);
return -1;
}
做笔记使用,如有差错或者更好理解的解法,还请多多指正!溜了溜了~~~