文章目录

  • LFUCache
  • 结构设计
  • LFUCache的实现

在之前我写过一篇LRU的博客,如果不了解的建议先看看这篇
高级数据结构与算法 | LRU缓存机制(Least Recently Used)

LFUCache

LFU(Least Frequently Used),即最不经常使用页置换算法。

如果一个数据在最近一段时间很少被访问到,那么可以认为在将来它被访问的可能性也很小。因此,当空间满时,最小频率访问的数据最先被淘汰,这就是LFU的核心思想。

如果我们想要设计一个LFU缓存算法,就需要满足以下功能

  • 初始化时设置缓存大小,当缓存已满后插入元素时需要淘汰现有元素来置换新元素
  • get(key):如果缓存中存在key,则获取value,并更新其使用次数
  • put(key, value) :如果key已经存在,则更新key所对应的value。如果不存在则插入键值对,但是插入时如果缓存已满,则需要将最不常用的键值对淘汰,置换新数据。如果存在多个使用频率相同的键值对,应当选取最久没有使用的。
  • 保证get和put的操作时间复杂度都为O(1)
class LFUCache {public:LFUCache(int capacity) {}int get(int key) {}void put(int key, int value) {}
};

根据以上条件,我们在上面给出了一个算法的大致框架,下面就结合框架以及条件,来设计我们具体的一个结构


结构设计

由于LFU需要根据使用次数来进行缓存淘汰的判断,因此我们每一项中不仅需要存储key,value,还需要存储使用次数freq

每一项的节点设计如下

class LFUNode
{public:LFUNode(int key, int value, int freq): _key(key), _value(value), _freq(freq){}int _key;   int _value;int _freq;  //访问次数
};

在上面列的几个条件中,其中最难处理的地方其实就是O(1)的get和put,因为没有任何一个独立的数据结构能够满足这个条件,因此我们需要组合使用多个结构。

如果要保证put中的插入和删除操作达到O(1),那么我们只能选择使用链表来记录我们的缓存信息,但是这次与之前的LRU并不相同,我们在保证使用次数最少的同时,还需要保证淘汰的时同次数中最久没有使用的,这就意味这我们需要根据不同的使用次数,来维护多个链表,并且每个链表中按照使用时间进行排序。

上面的设计依旧存在着一些问题,因为我们在对一个元素进行操作后,它的使用次数就会发生变化,那么我们就需要将其从原先的链表中移动到下一个链表,那么我们如何能够保证O(1)的查找到它,并对他进行操作呢?同时,我们如何能够快速的在多个链表中,查找到它所在的那个链表,以及它即将要去的链表呢?因此,我们还需要结合其他的数据结构来完成这个问题

要保证O(1)的查找链表,这时候就需要用到哈希结构了,我们可以将使用次数freq与每个保存该freq节点的链表建立起映射,保证O(1)的获取链表。

如果要保证O(1)的从指定链表中找到元素,并对其进行操作,我们可以将key与链表中对应元素的迭代器建立起映射,当我们通过key找到迭代器的时候,就可以利用迭代器对链表进行操作。

因此,最终LFUCache的结构设计如下

class LFUCache {public:LFUCache(int capacity) : _capacity(capacity), _minFreq(0){}int get(int key) {}void put(int key, int value) {}private:unordered_map<int, list<LFUNode>::iterator> _keyTable;  //建立key与节点迭代器的映射unordered_map<int, list<LFUNode>> _freqTable;           //建立freq与该值链表的映射int _minFreq;   //保存当前最小的freq,便于淘汰操作int _capacity;  //缓存最大容量
};

LFUCache的实现

接下来就该实现get和put功能了,我先列出这两个功能实现的核心思路

get

  • 根据keyTable查找到节点的迭代器,不存在则直接返回
  • 更新使用次数,通过迭代器将节点从原链表中删除,并通过freqTable获取下一个链表,将其头插进链表中,保证最后使用的在最前面,使得链表按照使用时间倒序排序
  • 将新链表中该节点的迭代器更新到keyTable中

put

  • 根据keyTable查找到节点的迭代器,存在则说明是更新,不存在则说明是淘汰
  • 如果是更新,那么操作的内容和上面的get大体相同
  • 如果是插入,需要判断当前缓存是否已满,是否需要淘汰,如果未满则直接插入freq=1的链表中,并将迭代器插入至keyTable中
  • 如果缓存已满,则需要根据minFreq在freqTable中找到对应的最小freq链表,并删除队尾,即最久没有使用的元素,同时在keyTable中淘汰该元素

这里我就将每一步的思路写在下面代码的注释中

完整的实现代码如下

class LFUNode
{public:LFUNode(int key, int value, int freq): _key(key), _value(value), _freq(freq){}int _key;   int _value;int _freq;  //访问次数
};class LFUCache {public:LFUCache(int capacity) : _capacity(capacity), _minFreq(0){}int get(int key) {if(_capacity == 0){return -1;}auto it = _keyTable.find(key);  //查找到对应的节点if(it != _keyTable.end()){list<LFUNode>::iterator node = it->second;         //记录节点的value以及freqint value = node->_value, freq = node->_freq;_freqTable[freq].erase(node); //从freq表中的对应链表删除当前节点if(_freqTable[freq].empty() && _minFreq == freq)  //如果删除后链表为空,则判断是否需要更新最小freq{_minFreq++;}_freqTable[freq + 1].push_front(LFUNode(key, value, freq + 1)); //将更新后的节点头插入下一个freq的链表中,保证按照插入时间排序it->second = _freqTable[freq + 1].begin();  //更新迭代器return value;}//查找不到直接返回-1else{return -1;}}void put(int key, int value) {if(_capacity == 0){return;}auto it = _keyTable.find(key);  //查找key是否存在,判断是更新还是插入if(it != _keyTable.end())   //如果存在,则是更新{list<LFUNode>::iterator node = it->second;int freq = node->_freq;_freqTable[freq].erase(node);if(_freqTable[freq].empty() && _minFreq == freq){_minFreq++;}_freqTable[freq + 1].push_front(LFUNode(key, value, freq + 1));it->second = _freqTable[freq + 1].begin();}else    //不存在则是插入{//如果缓存满了,则要先删除后才能插入if(_keyTable.size() == _capacity){//查询freq表,获取freq最少且最久没有使用的节点,即freq链表的尾部节点LFUNode node = _freqTable[_minFreq].back();_keyTable.erase(node._key);        //删除key表中的节点_freqTable[node._freq].pop_back();  //删除freq表中的节点}_freqTable[1].push_front(LFUNode(key, value, 1));          //在freq表中插入新节点_keyTable.insert(make_pair(key, _freqTable[1].begin()));//在key表中插入新节点_minFreq = 1;   //插入新元素,它为最小的}}
private:unordered_map<int, list<LFUNode>::iterator> _keyTable;  //建立key与节点迭代器的映射unordered_map<int, list<LFUNode>> _freqTable;           //建立freq与该值链表的映射int _minFreq;   //保存当前最小的freq,便于淘汰int _capacity;  //缓存最大容量
};

高级数据结构与算法 | LFU缓存机制(Least Frequently Used)相关推荐

  1. 高级数据结构与算法 | LRU缓存机制(Least Recently Used)

    文章目录 LRUCache的原理 LRUCache的实现 LRUCache的原理 LRU是Least Recently Used的缩写,意思是最近最少使用,它是一种Cache替换算法. 什么是 Cac ...

  2. lfu算法c语言,LeetCode算法系列 460. LFU 缓存机制

    力扣原题 460. LFU 缓存机制 请你为 最不经常使用(LFU)缓存算法设计并实现数据结构. 实现 LFUCache 类:LFUCache(int capacity) - 用数据结构的容量 cap ...

  3. 数据结构与算法 / LRU 缓存淘汰算法

    一.诞生原因 缓存是一种提供数据读取性能的技术,在硬件设计.软件开发中有广泛的应用,比如常见的 CPU 缓存,DB 缓存和浏览器缓存等.但是缓存的大小是有限的,需要一定的机制判断哪些数据需要淘汰,即: ...

  4. 【LeetCode】460 and 1132(LFU缓存机制)

    LRU 算法的淘汰策略是 Least Recently Used,也就是每次淘汰那些最久没被使⽤的数据: ⽽ LFU 算法的淘汰策略是 Least Frequently Used,也就是每次淘汰那些使 ...

  5. 高级数据结构与算法 | AVL树 (高度平衡树)

    文章目录 AVL树 实现思路 数据结构 查找 平衡因子 旋转 右旋 左旋 右左双旋 左右双旋 插入 删除 AVL树的验证 中序遍历 平衡判断 AVL树的性能 完整代码实现 AVL树 AVL树是最先发明 ...

  6. 高级数据结构与算法 | 跳跃表(Skip List)

    文章目录 区间查询时链表与顺序表的局限 跳表=链表+索引 跳表的原理 晋升 插入 删除 跳表的实现 跳表VS红黑树 区间查询时链表与顺序表的局限 假设有这样一个情景, 此时需要设计一个拍卖系统,对于商 ...

  7. 高级数据结构与算法 | 哈希 :哈希冲突、负载因子、哈希函数、哈希表、哈希桶

    文章目录 哈希 哈希函数 常见的哈希函数 字符串哈希函数 哈希冲突 闭散列的解决方法 开散列的解决方法 负载因子以及增容 对于闭散列 对于开散列结构 具体实现 哈希表(闭散列) 插入 查找 删除 完整 ...

  8. 高级数据结构与算法 | 回溯算法(Back Tracking Method)

    文章目录 回溯 电话号码的字母组合 二进制手表 组合总数 全排列 活字印刷 N皇后 N皇后II 回溯 回溯是一种通过穷举所有可能情况来找到所有解的算法.如果一个候选解最后被发现并不是可行解,回溯算法会 ...

  9. 高级数据结构与算法 | 深度遍历搜索(DFS)与广度遍历搜索(BFS)

    文章目录 深度优先搜索(DFS) 员工的重要性 图像渲染 岛屿的周长 被围绕的区域 岛屿数量 岛屿的最大面积 广度优先搜索(BFS) N叉树的层序遍历 腐烂的橘子 单词接龙 最小基因变化 打开转盘锁 ...

最新文章

  1. vue前端表格插件_Grid.js - 跨框架的前端表格插件
  2. Pycharm快捷键及一些常用设置
  3. A Learned Representation for Artistic Style论文理解
  4. 谷歌大脑发布神经网络的「核磁共振」,并公开相关代码
  5. 通过Shell开发企业级专业服务启动脚本案例(MySQL)
  6. python常用英语单词-python – 获取英语单词的基本形式
  7. Hive的下载安装,以及配置mysql作为元数据库
  8. SqliteHelper整理
  9. BZOJ2115XOR——线性基
  10. Module database cache not built yet, using slow search
  11. 上手测试GaussDB(for Redis) 和开源 Redis,只为推荐质优价廉的Redis
  12. c++list遍历_小白学PyTorch | 6 模型的构建访问遍历存储(附代码)
  13. NAT对数据业务的影响
  14. json字符串-单、双引号
  15. SCI 论文免费下载地址
  16. 车辆管理设备V系列JTT-808协议简介
  17. 算法篇-用栈来求解汉诺塔问题
  18. 查询计算机上可用端口的两种方法
  19. Android静态安全检查(九):不安全的SDCard存储检测
  20. 机器视觉中的像素、分辨率、灰度值等概念

热门文章

  1. 封装案例-完成开火方法
  2. Spring Boot整合Spring Data Redis-存取Java对象
  3. mysql 交集_MYSQL交集函数
  4. 摩根斯坦利面试题库_经验 | 金融公司摩根士丹利从笔试到实习的全程经验
  5. 210314阶段三VS使用Linux 的sqlite3 API
  6. 210130阶段三socket服务器
  7. Java获得时间 String与Timestamp互转
  8. POJ3070 矩阵快速幂模板
  9. Java 8 中的工厂方法模式
  10. CodeForces - 1486C2 Guessing the Greatest (hard version)(二分+交互)