导读:本期聚焦于小伙伴创作的《如何用C++实现高性能LFU缓存淘汰机制并分析频率链表的时间复杂度》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《如何用C++实现高性能LFU缓存淘汰机制并分析频率链表的时间复杂度》有用,将其分享出去将是对创作者最好的鼓励。

LFU缓存淘汰机制的核心原理

LFU(Least Frequently Used)缓存淘汰机制的核心逻辑是优先淘汰访问频率最低的数据,当多个数据频率相同时,通常再按照最近访问时间淘汰最久未访问的数据。相比LRU缓存仅关注访问时间,LFU更适配访问频率稳定的业务场景,比如静态资源缓存、热点数据缓存等。

如何用C++实现高性能LFU缓存淘汰机制并分析频率链表的时间复杂度

核心数据结构设计

要实现高性能的LFU缓存,需要结合三种核心结构:哈希表、频率链表、双向链表。哈希表用于快速定位缓存节点,频率链表按访问频率从小到大组织,每个频率节点下挂载一个双向链表,存储该频率下所有缓存节点的访问顺序。

  • 缓存节点:存储键、值、当前访问频率、前后指针
  • 频率节点:存储频率值、前后指针、该频率下的缓存节点链表头尾指针
  • 全局哈希表:键映射到缓存节点,快速查找
  • 最小频率变量:记录当前缓存中的最小访问频率,淘汰时快速定位

C++实现完整源码

以下是完整的LFU缓存实现代码,包含插入、访问、淘汰所有核心逻辑:

#include <unordered_map>
#include <iostream>

// 缓存节点定义
struct CacheNode {
    int key;
    int value;
    int freq;  // 当前访问频率
    CacheNode* prev;
    CacheNode* next;
    CacheNode(int k, int v) : key(k), value(v), freq(1), prev(nullptr), next(nullptr) {}
};

// 频率节点定义,每个频率对应一个双向链表存储该频率的缓存节点
struct FreqNode {
    int freq;  // 频率值
    FreqNode* prev;
    FreqNode* next;
    CacheNode* head;  // 该频率下缓存节点链表头(最近访问)
    CacheNode* tail;  // 该频率下缓存节点链表尾(最久未访问)
    FreqNode(int f) : freq(f), prev(nullptr), next(nullptr), head(nullptr), tail(nullptr) {}
};

class LFUCache {
private:
    int capacity;
    int minFreq;  // 当前最小访问频率
    std::unordered_map<int, CacheNode*> keyMap;  // 键到缓存节点的映射
    std::unordered_map<int, FreqNode*> freqMap;  // 频率到频率节点的映射
    FreqNode* freqHead;  // 频率链表头(最小频率)
    FreqNode* freqTail;  // 频率链表尾(最大频率)

    // 从缓存节点链表中移除指定节点
    void removeCacheNode(CacheNode* node, FreqNode* fNode) {
        if (node->prev) {
            node->prev->next = node->next;
        } else {
            // 是链表头节点
            fNode->head = node->next;
        }
        if (node->next) {
            node->next->prev = node->prev;
        } else {
            // 是链表尾节点
            fNode->tail = node->prev;
        }
        // 如果该频率下没有缓存节点了,删除频率节点
        if (fNode->head == nullptr) {
            removeFreqNode(fNode);
            // 如果删除的是最小频率节点,更新最小频率
            if (fNode->freq == minFreq) {
                minFreq++;
            }
        }
        delete node;
    }

    // 从频率链表中移除指定频率节点
    void removeFreqNode(FreqNode* fNode) {
        if (fNode->prev) {
            fNode->prev->next = fNode->next;
        } else {
            freqHead = fNode->next;
        }
        if (fNode->next) {
            fNode->next->prev = fNode->prev;
        } else {
            freqTail = fNode->prev;
        }
        freqMap.erase(fNode->freq);
        delete fNode;
    }

    // 将缓存节点添加到指定频率节点的缓存链表头部(最近访问位置)
    void addCacheToFreqHead(CacheNode* node, FreqNode* fNode) {
        node->next = fNode->head;
        node->prev = nullptr;
        if (fNode->head) {
            fNode->head->prev = node;
        } else {
            // 链表为空,头尾都指向该节点
            fNode->tail = node;
        }
        fNode->head = node;
    }

    // 新增频率节点到频率链表头部(最小频率位置)
    FreqNode* addFreqNodeToHead(int freq) {
        FreqNode* newNode = new FreqNode(freq);
        newNode->next = freqHead;
        if (freqHead) {
            freqHead->prev = newNode;
        } else {
            freqTail = newNode;
        }
        freqHead = newNode;
        freqMap[freq] = newNode;
        return newNode;
    }

public:
    LFUCache(int cap) : capacity(cap), minFreq(0), freqHead(nullptr), freqTail(nullptr) {}

    ~LFUCache() {
        // 释放所有缓存节点
        for (auto& pair : keyMap) {
            delete pair.second;
        }
        // 释放所有频率节点
        FreqNode* cur = freqHead;
        while (cur) {
            FreqNode* next = cur->next;
            delete cur;
            cur = next;
        }
    }

    int get(int key) {
        if (capacity <= 0) return -1;
        auto it = keyMap.find(key);
        if (it == keyMap.end()) {
            return -1;
        }
        CacheNode* node = it->second;
        // 更新节点频率
        int oldFreq = node->freq;
        FreqNode* oldFreqNode = freqMap[oldFreq];
        // 从旧频率节点中移除该缓存节点
        removeCacheNode(node, oldFreqNode);
        // 新频率
        int newFreq = oldFreq + 1;
        node->freq = newFreq;
        // 查找或创建新频率节点
        FreqNode* newFreqNode = nullptr;
        auto freqIt = freqMap.find(newFreq);
        if (freqIt == freqMap.end()) {
            // 频率节点不存在,创建并插入到频率链表对应位置(按频率递增,插入到旧频率节点后面)
            newFreqNode = new FreqNode(newFreq);
            newFreqNode->prev = oldFreqNode;
            newFreqNode->next = oldFreqNode->next;
            if (oldFreqNode->next) {
                oldFreqNode->next->prev = newFreqNode;
            } else {
                freqTail = newFreqNode;
            }
            oldFreqNode->next = newFreqNode;
            freqMap[newFreq] = newFreqNode;
        } else {
            newFreqNode = freqIt->second;
        }
        // 将缓存节点添加到新频率节点的链表头部
        addCacheToFreqHead(node, newFreqNode);
        return node->value;
    }

    void put(int key, int value) {
        if (capacity <= 0) return;
        auto it = keyMap.find(key);
        if (it != keyMap.end()) {
            // 键已存在,更新值并调用get逻辑更新频率
            it->second->value = value;
            get(key);
            return;
        }
        // 键不存在,需要插入新节点
        if (keyMap.size() >= capacity) {
            // 缓存已满,执行淘汰:淘汰最小频率下的最久未访问节点
            FreqNode* minFreqNode = freqMap[minFreq];
            CacheNode* toRemove = minFreqNode->tail;
            keyMap.erase(toRemove->key);
            removeCacheNode(toRemove, minFreqNode);
        }
        // 创建新缓存节点,频率为1
        CacheNode* newNode = new CacheNode(key, value);
        // 查找频率1的节点是否存在
        auto freqIt = freqMap.find(1);
        FreqNode* freq1Node = nullptr;
        if (freqIt == freqMap.end()) {
            // 频率1节点不存在,创建并放到频率链表头部
            freq1Node = addFreqNodeToHead(1);
            minFreq = 1;  // 新插入的节点频率为1,是最小频率
        } else {
            freq1Node = freqIt->second;
        }
        // 将新节点添加到频率1节点的链表头部
        addCacheToFreqHead(newNode, freq1Node);
        keyMap[key] = newNode;
    }
};

频率链表相关操作的时间复杂度分析

我们通过频率链表的特性来分析核心操作的时间复杂度:

操作类型时间复杂度说明
get操作O(1)哈希表查找缓存节点O(1),频率链表和缓存链表的插入删除都是指针操作O(1),整体常数时间
put操作(插入新键)O(1)哈希表插入O(1),缓存满时淘汰最小频率节点也是O(1),其余操作和get一致
put操作(更新已有键)O(1)等同于先get再更新值,所有操作都是常数时间
淘汰操作O(1)通过minFreq变量直接定位最小频率节点,再取该节点链表尾的节点淘汰,无需遍历

实现注意事项

在实际使用上述实现时,需要注意以下几点:

  • 当缓存容量为0时,所有操作都应直接返回空或失败,避免无效操作
  • 频率节点的删除需要同步更新minFreq变量,否则会出现淘汰时找不到最小频率节点的问题
  • 缓存节点和频率节点都需要手动管理内存,避免内存泄漏,析构函数中需要遍历释放所有节点
  • 如果业务场景需要支持更多类型的键值对,可以将实现改为模板类,适配不同数据类型

总结

基于频率链表和哈希表的C++ LFU缓存实现,所有核心操作都能达到O(1)的时间复杂度,性能表现优异。频率链表的设计避免了每次更新频率时遍历所有节点的开销,通过最小频率变量快速定位淘汰目标,整体逻辑清晰且高效。开发者可以根据自身业务需求调整节点存储的内容,比如添加过期时间、访问时间戳等扩展功能。

LFU_cacheC++频率链表时间复杂度分析缓存淘汰修改时间:2026-06-10 01:42:50

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。