LFU缓存

一. 使用两个哈希实现

一个哈希进行直接索引,另一个哈希根据访问频率索引双向链表

/*
定义Node类 双链表节点,包含键、值、前驱、后继

定义LFUCache 类
变量
min_freq:当前最小频率层次
capacity:容量
key_to_node:根据键值索引节点的哈希
freq_to_dummy:根据频率索引双链表的哈希

方法
get_node:根据键获取并更新节点
new_list :创建一个新的双链表头结点
push_front:在对应频率的双链表上插入节点
remove :在链表结构中删除该节点,如果链表为空,删除该链表
release : 释放出一个内存

get: 根据键获取对应值
put: 插入键值对
*/

class Node { //双向链表节点,同时要存储键和值
public:
    int key, value, freq = 1; 
    Node *prev, *next;
    Node(int k = 0, int v = 0) : key(k), value(v) {}
};

class LFUCache {
private:
    int min_freq;//记录最小一层的频率,用于移除释放
    int capacity;
    unordered_map<int, Node*> key_to_node;  //一个哈希直接找对应节点
    unordered_map<int, Node*> freq_to_dummy; //一个哈希找对应频率的双链表

    Node *get_node(int key) { //获取并更新节点
        auto it = key_to_node.find(key); //直接通过键值哈希查找
        if (it == key_to_node.end())  // 没有该节点
            return nullptr;
        auto node = it->second; // 有该节点
        remove(node); // 从当前链表抽出

        //找该节点的双链表
        push_front(++node->freq, node); // 放到下一层最前面
        return node;
    }

    // 创建一个新的双向链表头尾节点
    Node *new_list() {
        auto dummy = new Node(); // 哨兵节点
        dummy->prev = dummy;
        dummy->next = dummy;
        return dummy;
    }

    // 在对应频率的链表头添加一个节点
    void push_front(int freq, Node *x) {
        auto it = freq_to_dummy.find(freq);
        if (it == freq_to_dummy.end())  // 这个哈希还没有初始化双链表
            it = freq_to_dummy.emplace(freq, new_list()).first;

        //获取对应的双链表,并添加至头部(头插法)
        auto dummy = it->second;
        x->prev = dummy;
        x->next = dummy->next;
        x->prev->next = x;
        x->next->prev = x;
    }

    // 删除一个节点,删除在链表中的关系,同时链表为空后删除链表
    void remove(Node *x) {
        x->prev->next = x->next;
        x->next->prev = x->prev;

        //主要是判断更新当前最小频率双链表
        auto dummy = freq_to_dummy[x->freq];
        if (dummy->prev == dummy) { // 抽出来后,双链表为空
            freq_to_dummy.erase(x->freq); // 移除空链表
            delete dummy; // 释放内存
            if (min_freq == x->freq) //更新最小频率
                min_freq++;
        }
    }

    void release(){ //频率最小的双链表上,最后节点进行释放
        auto dummy = freq_to_dummy[min_freq];
        auto back_node = dummy->prev; // 找最后的节点
        key_to_node.erase(back_node->key); // 移除在哈希上的关系,这里relea直接删除,remove并不会直接删除
        remove(back_node); // 移除
        delete back_node;
    }

public:
    LFUCache(int capacity) : capacity(capacity) {}

    int get(int key) {
        auto node = get_node(key);
        return node ? node->value : -1;
    }

    void put(int key, int value) {
        auto node = get_node(key);
        if (node) { // 有这个节点
            node->value = value; // 更新 value
            return;
        }
        if (key_to_node.size() == capacity)  //释放内存
            release();
        
        key_to_node[key] = node = new Node(key, value); // 新节点
        push_front(1, node); // 放在「看过 1 次」的最上面
        min_freq = 1;
    }
};
posted @ 2023-09-25 13:10  失控D大白兔  阅读(9)  评论(0编辑  收藏  举报