C++ 实现LRU 与 LFU 的缓存算法

C++ 实现LRU 与 LFU 的缓存算法

算法描述

LRU和LFU是常用的缓存算法。它们能够优化系统读写速度,提高系统效率。

LRU

LRU (Least Recent Used)是最近最少使用算法,维护一个缓存队列,每次访问缓存中的一个元素时,将其移动到队列的头部,当缓存队列满时删除队尾元素,保证最近使用过的元素在缓存队列的最前面,最近没有使用过的元素在缓存队列的最后面。

LFU

LFU (Least Frequently Used)是利用元素访问次数的算法,维护一个缓存队列,每次访问缓存中的一个元素时,将该元素的访问次数增加1。当缓存队列满时,删除访问次数最少的元素。这种算法可以有效地处理热点数据的缓存问题。

算法实现

下面分别实现LRU和LFU算法。

LRU

使用哈希表和双向链表实现LRU算法,哈希表维护元素在链表中的位置,双向链表维护元素的访问顺序。

class Node { // 链表节点
public:
    int key, val;
    Node* prev;
    Node* next;
    Node(int k, int v) : key(k), val(v), prev(nullptr), next(nullptr) {}
};

class LRUCache {
public:
    LRUCache(int capacity) : cap(capacity) {}

    int get(int key) {
        if (!hash.count(key)) return -1; // 未找到元素
        Node* node = hash[key]; // 取出节点
        update(node); // 更新节点访问顺序
        return node->val;
    }

    void put(int key, int value) {
        if (hash.count(key)) {
            Node* node = hash[key]; // 取出节点
            node->val = value; // 更新节点
            update(node); // 更新节点访问顺序
        }
        else {
            if (list.size() == cap) { // 缓存队列已满
                Node* node = list.back(); // 取出队尾节点
                hash.erase(node->key); // 从哈希表中删除
                list.pop_back(); // 从链表中删除
                delete node; // 释放节点
            }
            Node* node = new Node(key, value); // 创建新节点
            list.push_front(node); // 新节点插入到队头
            hash[key] = node; // 将新节点加入哈希表
        }
    }

private:
    int cap; // 缓存容量大小
    list<Node*> list; // 双向链表,维护元素访问顺序
    unordered_map<int, Node*> hash; // 哈希表,记录元素在链表中的位置

    // 更新节点访问顺序
    void update(Node* node) {
        list.erase(node); // 先将节点从链表中删除
        list.push_front(node); // 将节点插入到队头
    }
};

LFU

使用哈希表、桶和两个双向链表实现LFU算法,哈希表维护元素在桶中的位置,每个桶记录访问次数相同的元素,一个双向链表维护桶的访问顺序,另一个双向链表维护元素的访问顺序。

class Node { // 链表节点
public:
    int key, val, freq;
    Node* prev;
    Node* next;
    Node(int k, int v, int f) : key(k), val(v), freq(f), prev(nullptr), next(nullptr) {}
};

class LFUCache {
public:
    LFUCache(int capacity) : cap(capacity), minFreq(0) {}

    int get(int key) {
        if (!hash.count(key)) return -1; // 未找到元素
        Node* node = hash[key]; // 取出节点
        update(node); // 更新节点访问顺序
        return node->val;
    }

    void put(int key, int value) {
        if (cap == 0) return;
        if (hash.count(key)) {
            Node* node = hash[key]; // 取出节点
            node->val = value; // 更新节点
            update(node); // 更新节点访问顺序
        }
        else {
            if (hash.size() == cap) { // 缓存队列已满
                DLList& list = freq[listHead->next->freq]; // 取出访问频率最小的桶
                Node* node = list.removeTail(); // 取出队尾节点
                hash.erase(node->key); // 从哈希表中删除
                delete node; // 释放节点
            }
            Node* node = new Node(key, value, 1); // 创建新节点
            freq[1].addHead(node); // 将新节点插入到访问频率为1的桶的头部
            hash[key] = node; // 将新节点加入哈希表
            listHead = &freq[1]; // 更新访问频率最小的桶
        }
    }

private:
    int cap; // 缓存容量大小
    int minFreq; // 最小访问频率
    DLList* listHead; // 访问频率最小的桶
    unordered_map<int, Node*> hash; // 哈希表,记录元素在桶中的位置
    unordered_map<int, DLList> freq; // 桶,记录访问频率相同的元素

    class DLList { // 双向链表
    public:
        DLList() : head(new Node(0, 0, 0)), tail(new Node(0, 0, 0)), size(0) { // 头尾哨兵节点
            head->next = tail;
            tail->prev = head;
        }

        void addHead(Node* node) { // 在头部添加节点
            node->next = head->next;
            node->prev = head;
            head->next->prev = node;
            head->next = node;
            ++size;
        }

        Node* removeTail() { // 删除尾部节点
            if (size == 0) return nullptr;
            Node* node = tail->prev;
            remove(node);
            return node;
        }

        void remove(Node* node) { // 删除指定节点
            node->prev->next = node->next;
            node->next->prev = node->prev;
            --size;
        }

        bool empty() const { return head->next == tail; }

    private:
        Node* head; // 头哨兵节点
        Node* tail; // 尾哨兵节点
        size_t size; // 链表长度
    };

    // 更新节点访问顺序
    void update(Node* node) {
        DLList& list = freq[node->freq]; // 取出节点所在的桶
        list.remove(node); // 从当前桶中删除节点
        if (list.empty() && node->freq == minFreq) { // 当前桶为空且节点所在桶是访问频率最小的桶
            ++minFreq; // 最小访问频率加1
            listHead = &freq[minFreq]; // 更新访问频率最小的桶
        }
        ++node->freq; // 节点访问频率加1
        freq[node->freq].addHead(node); // 将节点加入新桶的头部
    }
};

示例说明

下面给出两个示例说明。

示例1

LRUCache lruCache(2);
lruCache.put(1, 1);
lruCache.put(2, 2);
assert(lruCache.get(1) == 1); // 返回 1
lruCache.put(3, 3); // 缓存容量已满,需要删除最近最少使用的元素 2
assert(lruCache.get(2) == -1); // 返回 -1,因为元素 2 已经被删除
lruCache.put(4, 4); // 缓存容量已满,需要删除最近最少使用的元素 1
assert(lruCache.get(1) == -1); // 返回 -1,因为元素 1 已经被删除
assert(lruCache.get(3) == 3); // 返回 3
assert(lruCache.get(4) == 4); // 返回 4

示例2

LFUCache lfuCache(2);
lfuCache.put(1, 1);
lfuCache.put(2, 2);
assert(lfuCache.get(1) == 1); // 返回 1
lfuCache.put(3, 3); // 缓存容量已满,需要删除访问频率最小的元素 2
assert(lfuCache.get(2) == -1); // 返回 -1,因为元素 2 已经被删除
assert(lfuCache.get(3) == 3); // 返回 3
lfuCache.put(4, 4); // 缓存容量已满,需要删除访问频率最小的元素 1
assert(lfuCache.get(1) == -1); // 返回 -1,因为元素 1 已经被删除
assert(lfuCache.get(3) == 3); // 返回 3
assert(lfuCache.get(4) == 4); // 返回 4
lfuCache.put(3, 33); // 更新元素 3 的值,访问频率加1
assert(lfuCache.get(3) == 33); // 返回 33
assert(lfuCache.get(4) == 4); // 返回 4
lfuCache.put(2, 22); // 更新元素 2 的值,访问频率加1
assert(lfuCache.get(2) == 22); // 返回 22

以上就是C++实现LRU与LFU的缓存算法的完整攻略。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:C++ 实现LRU 与 LFU 的缓存算法 - Python技术站

(0)
上一篇 2023年5月22日
下一篇 2023年5月22日

相关文章

  • C语言零基础入门(2)

    当学习C语言的时候,需要掌握很多基础知识,掌握这些知识才能正常地写出代码。本文将解释C语言的入门知识。 变量 变量指代内存数据。变量有多个类型,包括整数、浮点数、字符等等。编程时必须考虑变量的类型,这会对程序产生不同的影响。 声明变量 在C语言中,需要先声明一个变量,然后才能使用它,如下所示: int num; float x; char letter; 这…

    C 2023年5月23日
    00
  • QT连接Mysql数据库的实现步骤

    好的。首先,我们需要安装 Qt 和 mysql 的相关驱动程序。安装完后,我们可以开始进行以下步骤: 步骤一:加载 mysql 驱动 在 Qt 中连接 mysql 数据库之前,我们需要在程序中先加载 mysql 驱动。在通常情况下,mysql 驱动是通过插件的方式来实现的。我们需要在项目的.pro 文件中加入以下代码: QT += sql QT += sql…

    C 2023年5月23日
    00
  • 怎么解决应用程序发生异常 未知的软件异常 (0xc0000409),位置为0x00409b14的问题

    解决应用程序发生异常未知的软件异常(0xc0000409)是一个比较常见的问题,下面详细讲解解决这个问题的完整攻略。 问题原因分析 应用程序发生异常未知的软件异常(0xc0000409)是由于应用程序所调用的未知的软件异常导致的。这个异常通常是由于应用程序错误、病毒或者不兼容的驱动程序引起的。 解决方案 方案一:升级应用程序 如果出现了应用程序发生异常未知的…

    C 2023年5月23日
    00
  • C语言实现超市计价收款系统

    C语言实现超市计价收款系统攻略 简介 本文将介绍如何使用C语言实现一个简单的超市计价收款系统。该系统将能够记录商品信息、价格以及计算顾客的购物总价等功能。 主要步骤 以下是实现该系统的主要步骤: 定义结构体 定义商品信息的结构体,包括商品名、价格等信息。例如: struct goods { char name[20]; int price; int num;…

    C 2023年5月23日
    00
  • C程序 冒泡排序

    以下是详细讲解“C程序 冒泡排序”的完整使用攻略。 冒泡排序概述 冒泡排序是一种简单的排序算法,它重复地遍历要排序的序列,一次比较两个元素,如果它们的顺序错误就把它们交换过来,直到没有元素需要交换,排序完成。 冒泡排序的时间复杂度为O(n²)。 以下是C语言中实现冒泡排序的代码示例: void bubble_sort(int *arr, int n) { i…

    C 2023年5月9日
    00
  • C语言中如何进行排序和查找操作?

    C语言中进行排序和查找操作是非常常见和重要的操作,下面我将详细介绍排序和查找操作的常见方法和算法。 排序算法 冒泡排序 冒泡排序是一种简单的排序算法,它的基本思想是通过依次比较相邻的元素,将较大的元素后移,较小的元素前移,达到排序的目的。冒泡排序时间复杂度为O(n^2),是一种效率较低的算法。 示例代码: void bubble_sort(int array…

    C 2023年4月27日
    00
  • C++面向对象之类和对象那些你不知道的细节原理详解

    C++面向对象之类和对象那些你不知道的细节原理详解 什么是类 类是C++中定义自己的数据类型的方法。类可看作是一种用户自定义的数据结构。 我们可以通过定义变量来定义一个类的对象,这个对象就包含了类的属性和操作。 类的基本组成 成员变量 成员变量是类的属性,也称为数据成员、字段或属性。 相当于结构体中的成员变量,可以是任何C++数据类型,包括另一个类的对象。 …

    C 2023年5月23日
    00
  • C++实现KFC点餐系统

    C++实现KFC点餐系统 介绍 KFC点餐系统是一个比较基础和实用的点餐系统,程序的主要功能是菜单的展示,菜品的选购和账单的结算,适合初学者学习C++的面向对象编程思想。 设计 系统主要结构通过类和对象来实现,主要包括管理类,订单类, 菜品类和顾客类,其中管理类为整个系统的核心,负责菜单的初始化和展示、订单管理以及结算等操作。 核心功能 1. 菜单初始化和展…

    C 2023年5月23日
    00
合作推广
合作推广
分享本页
返回顶部