LFU(最不经常使用) 缓存算法实现

上一篇博客我们实现了LRU缓存算法。

进一步地,我们思考一下LFU算法又该如何实现呢?与LRU算法相比,LFU算法要求当缓存达到其容量 capacity 时,则应该在插入新项之前,移除最不经常使用 的项。在此问题中,当存在平局(即两个或更多个键具有相同使用频率)时,应该去除 最久未使用 的键。也就是说LFU算法实现比LRU算法要考虑的更多一步,也就是要考虑每个缓存内容的使用频率。

思路

在上一篇LRU算法实现过程中,我们提到了可以将缓存的更新和替换看成对一堆书进行操作,每次get和put已有的书时,则将书放到书堆的最上面,当有新的书加入时,则将最下面的书取出,并将新的书放到最上面。

对于LFU算法的实现,我们同样可以将其看成对书堆的操作,不同的是,我们需要看成对多个书堆的操作,每本书用一个使用频率freq 来进行维护其使用次数。当对一本书进行get和put操作时,其频率加一,同时每个书堆分别代表不同使用频率的书的集合,每个集合则用LRU算法进行维护书堆。

那么。

  1. 如何实现好几堆书呢?

同样,可以用哈希表实现,哈希表的键表示频率,哈希表的值则是双链表的头结点。这样我们就能在O(1)的时间复杂度获取相应频率的书堆。

  1. 超过缓存容量时,如何快速获取到使用频率最小的书呢?

我们可以维护一个min_freq的变量记录使用频率最小的数。在添加一本新书的情况下,这本新书一定是放在 f r e q = 1 freq=1 freq=1 的那摞书上,此时我们把 min_freq置为 1。在「抽出一本书且这摞书变成空」的情况下,我们会把这本书放到它右边这摞书的最上面。如果变成空的那摞书是最左边的,我们还会把 min_freq加一。所以无论如何,min_freq都会对应着最左边的非空的那摞书。相应的,如果有新的书放到这几个书堆中来,则这本新书的使用频率肯定是最小的并且为1,此时可以更新min_freq的值为1。

算法分析

  • 时间复杂度:所有操作均为O(1)

  • 空间复杂度:O(capacity)

  • 淘汰策略:

    优先淘汰最低频率节点

    同频率时淘汰最久未使用

优化亮点

  1. 双哈希表结构:实现快速访问和频率管理

  2. 哨兵节点:简化链表边界操作

  3. 动态频率维护:自动跟踪最小频率

  4. LRU辅助策略:在相同频率下保留访问时序

代码实现

cpp 复制代码
class Node {
public:
    int key; 
    int value;
    int freq;
    Node *next;
    Node *prev;
    Node(int k = 0, int v = 0, int f = 1) : key(k), value(v), freq(f){}
};
class LFUCache {
private:
    int capacity;
    int min_freq;
    unordered_map <int, Node*> key_to_node;
    unordered_map <int, Node*> freq_to_node;

    void RemoveNode(int key) {
        Node *node = key_to_node[key];
        node -> prev -> next = node -> next;
        node -> next -> prev = node -> prev;
        int freq = node -> freq;
        key_to_node.erase(key);
        if(freq_to_node[min_freq] -> next == freq_to_node[min_freq]) {
            delete freq_to_node[min_freq];
            freq_to_node.erase(min_freq);
            // 如果被删除的是当前最小频率链表,则最小频率+1
            if(min_freq == freq) min_freq++;
        }
        delete node;
    }

    void PushFront(Node *node) { // 头插法
        Node *cache;
        int freq = node -> freq;
        // 如果该频率没有链表,则新建哨兵节点
        if(freq_to_node.find(freq) == freq_to_node.end()) {
            cache = new Node(0, 0); // 创建一个哨兵结点
            cache -> next = cache -> prev = cache;
            freq_to_node[freq] = cache;
        } else {
            cache = freq_to_node[freq];
        }
        node -> next = cache -> next;
        node -> prev = cache;
        cache -> next -> prev = node;
        cache -> next = node;
        key_to_node[node -> key] = node;
    }

public:
    LFUCache(int capacity) {
        this -> capacity = capacity;
        min_freq = 1;  // 初始最小频率为1(新节点频率都是1)
    }
    
    int get(int key) {
        if(key_to_node.find(key) != key_to_node.end()) {
            int value = key_to_node[key] -> value;
            int freq = key_to_node[key] -> freq + 1;
            RemoveNode(key);  // 从旧频率链表移除
            Node *node = new Node(key, value, freq);
            PushFront(node);   // 插入新频率链表
            return value;
        }
        return -1;
    }
    
    void put(int key, int value) {
        auto find_key = key_to_node.find(key);
        if(find_key == key_to_node.end()) {
            Node *node = new Node(key, value);
            if(key_to_node.size() < capacity) {
                PushFront(node);
            } else {
                RemoveNode(freq_to_node[min_freq] -> prev -> key);
                PushFront(node);
            }
            min_freq = 1; // 如果是新内容
        } else { // 如果内容已经存在
            int freq = key_to_node[key] -> freq;
            RemoveNode(key);
            Node *node = new Node(key, value, freq + 1);
            PushFront(node);
        }
    }
};

/**
 * 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);
 */
相关推荐
eamon1001 小时前
麒麟V10 arm cpu aarch64 下编译 RocketMQ-Client-CPP 2.2.0
c++·rocketmq·java-rocketmq
laimaxgg1 小时前
Qt窗口控件之颜色对话框QColorDialog
开发语言·前端·c++·qt·命令模式·qt6.3
乌云暮年2 小时前
算法刷题整理合集(四)
java·开发语言·算法·dfs·bfs
梁山1号3 小时前
【QT】】qcustomplot的初步使用二
c++·单片机·qt
bryant_meng3 小时前
【C++】Virtual function and Polymorphism
c++·多态·抽象类·虚函数·纯虚函数
Luo_LA3 小时前
【排序算法对比】快速排序、归并排序、堆排序
java·算法·排序算法
AI技术控3 小时前
机器学习算法实战——天气数据分析(主页有源码)
算法·机器学习·数据分析
oioihoii3 小时前
C++20 中的同步输出流:`std::basic_osyncstream` 深入解析与应用实践
c++·算法·c++20
猪猪成3 小时前
【图论】FLOYD弗洛伊德算法-最短路径
学习·算法·图论
HR Zhou3 小时前
群体智能优化算法-粒子群优化算法(Particle Swarm Optimization, PSO,含Matlab源代码)
算法·matlab·优化·智能优化算法