题目描述
请你为 最不经常使用(LFU)缓存算法设计并实现数据结构。
实现 LFUCache 类:
LFUCache(int capacity) - 用数据结构的容量 capacity 初始化对象
为了确定最不常使用的键,可以为缓存中的每个键维护一个 使用计数器 。使用计数最小的键是最久未使用的键。
当一个键首次插入到缓存中时,它的使用计数器被设置为 1 (由于 put 操作)。对缓存中的键执行 get 或 put 操作,使用计数器的值将会递增。
示例:
输入:
[“LFUCache”, “put”, “put”, “get”, “put”, “get”, “get”, “put”, “get”, “get”, “get”]
[[2], [1, 1], [2, 2], [1], [3, 3], [2], [3], [4, 4], [1], [3], [4]]
输出:
[null, null, null, 1, null, -1, 3, null, -1, 3, 4]
解释:
// cnt(x) = 键 x 的使用计数
// cache=[] 将显示最后一次使用的顺序(最左边的元素是最近的)
LFUCache lFUCache = new LFUCache(2);
lFUCache.put(1, 1); // cache=[1,_], cnt(1)=1
lFUCache.put(2, 2); // cache=[2,1], cnt(2)=1, cnt(1)=1
lFUCache.get(1); // 返回 1
// cache=[1,2], cnt(2)=1, cnt(1)=2
lFUCache.put(3, 3); // 去除键 2 ,因为 cnt(2)=1 ,使用计数最小
// cache=[3,1], cnt(3)=1, cnt(1)=2
lFUCache.get(2); // 返回 -1(未找到)
lFUCache.get(3); // 返回 3
// cache=[3,1], cnt(3)=2, cnt(1)=2
lFUCache.put(4, 4); // 去除键 1 ,1 和 3 的 cnt 相同,但 1 最久未使用
// cache=[4,3], cnt(4)=1, cnt(3)=2
lFUCache.get(1); // 返回 -1(未找到)
lFUCache.get(3); // 返回 3
// cache=[3,4], cnt(4)=1, cnt(3)=3
lFUCache.get(4); // 返回 4
// cache=[3,4], cnt(4)=2, cnt(3)=3
清华操作系统视频
比较复杂,使用的是双链表的数据结构,但是每个双链表里面的节点又是由双链表组成的,里层的双链表结构类似于LRU的双链表,外层的双链表是通过节点的个数区分的,也就是说内层的双链表节点的个数是一致的。
外层节点
内层节点(类似于LRU算法)
如果外层节点为空需要将该外层节点全部删除,不然遍历的时候全是空节点
class LFUCache {
public:
struct Node {
Node *left, *right;
int key, val;
Node(int _key, int _val) {
key = _key, val = _val;
left = right = NULL;
}
};//创建内层节点的双链表
struct Block {
Block *left, *right;
Node *head, *tail;
int cnt;
Block(int _cnt) {
cnt = _cnt;
left = right = NULL;
head = new Node(-1, -1), tail = new Node(-1, -1);
head->right = tail, tail->left = head;
}//初始化块
~Block() {
delete head;
delete tail;
}//当对象的生命周期结束时,撤销类对象时候会自动调用析构函数。
void insert(Node* p) {
p->right = head->right;
head->right->left = p;
p->left = head;
head->right = p;
}
void remove(Node* p) {
p->left->right = p->right;
p->right->left = p->left;
}
bool empty() {
return head->right == tail;
}
}*head, *tail;
int n;
unordered_map<int, Block*> hash_block;
unordered_map<int, Node*> hash_node;
void insert(Block* p) { // 在p的右侧插入新块,cnt是p->cnt + 1
auto cur = new Block(p->cnt + 1);
cur->right = p->right;
p->right->left = cur;
p->right = cur;
cur->left = p;
}
void remove(Block* p) {
p->left->right = p->right;
p->right->left = p->left;
delete p;
}
LFUCache(int capacity) {
n = capacity;
head = new Block(0), tail = new Block(INT_MAX);
head->right = tail, tail->left = head;//一开始外层节点没有任何内容
}
int get(int key) {
if (hash_block.count(key) == 0) return -1;
auto block = hash_block[key];
auto node = hash_node[key];
block->remove(node);
if (block->right->cnt != block->cnt + 1) insert(block);
block->right->insert(node);
hash_block[key] = block->right;
if (block->empty()) remove(block);//如果当前块删除了内层节点后变空,那么该块也需要被删除。
return node->val;
}
void put(int key, int value) {
if (!n) return;//如果缓存空间为0,那么直接返回即可
if (hash_block.count(key)) {
hash_node[key]->val = value;
get(key);
} //如果块中有该值,那么直接使用get方法即可,意思是块加一操作
else
{
if (hash_block.size() == n)
{
auto p = head->right->tail->left;
head->right->remove(p);
if (head->right->empty()) remove(head->right);
hash_block.erase(p->key);
hash_node.erase(p->key);
delete p;
}//内存满的情况,删除块里最右边的节点
//反之,创建节点,如果没有cnt==1的块,创建块。
//在cnt==1的块中起始位置插入节点
//修改块hash表和内层节点hash表
auto p = new Node(key, value);
if (head->right->cnt > 1) insert(head);
head->right->insert(p);
hash_block[key] = head->right;
hash_node[key] = p;
}
}
};
/**
* Your LFUCache object will be instantiated and called as such:
* LFUCache* obj = new LFUCache(capacity);
* int param_1 = obj->get(key);
* obj->put(key,value);
*/
链接:https://www.acwing.com/activity/content/code/content/555766/