高级数据结构与算法 | LFU缓存机制(Least Frequently Used)
文章目录
- 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)相关推荐
- 高级数据结构与算法 | LRU缓存机制(Least Recently Used)
文章目录 LRUCache的原理 LRUCache的实现 LRUCache的原理 LRU是Least Recently Used的缩写,意思是最近最少使用,它是一种Cache替换算法. 什么是 Cac ...
- lfu算法c语言,LeetCode算法系列 460. LFU 缓存机制
力扣原题 460. LFU 缓存机制 请你为 最不经常使用(LFU)缓存算法设计并实现数据结构. 实现 LFUCache 类:LFUCache(int capacity) - 用数据结构的容量 cap ...
- 数据结构与算法 / LRU 缓存淘汰算法
一.诞生原因 缓存是一种提供数据读取性能的技术,在硬件设计.软件开发中有广泛的应用,比如常见的 CPU 缓存,DB 缓存和浏览器缓存等.但是缓存的大小是有限的,需要一定的机制判断哪些数据需要淘汰,即: ...
- 【LeetCode】460 and 1132(LFU缓存机制)
LRU 算法的淘汰策略是 Least Recently Used,也就是每次淘汰那些最久没被使⽤的数据: ⽽ LFU 算法的淘汰策略是 Least Frequently Used,也就是每次淘汰那些使 ...
- 高级数据结构与算法 | AVL树 (高度平衡树)
文章目录 AVL树 实现思路 数据结构 查找 平衡因子 旋转 右旋 左旋 右左双旋 左右双旋 插入 删除 AVL树的验证 中序遍历 平衡判断 AVL树的性能 完整代码实现 AVL树 AVL树是最先发明 ...
- 高级数据结构与算法 | 跳跃表(Skip List)
文章目录 区间查询时链表与顺序表的局限 跳表=链表+索引 跳表的原理 晋升 插入 删除 跳表的实现 跳表VS红黑树 区间查询时链表与顺序表的局限 假设有这样一个情景, 此时需要设计一个拍卖系统,对于商 ...
- 高级数据结构与算法 | 哈希 :哈希冲突、负载因子、哈希函数、哈希表、哈希桶
文章目录 哈希 哈希函数 常见的哈希函数 字符串哈希函数 哈希冲突 闭散列的解决方法 开散列的解决方法 负载因子以及增容 对于闭散列 对于开散列结构 具体实现 哈希表(闭散列) 插入 查找 删除 完整 ...
- 高级数据结构与算法 | 回溯算法(Back Tracking Method)
文章目录 回溯 电话号码的字母组合 二进制手表 组合总数 全排列 活字印刷 N皇后 N皇后II 回溯 回溯是一种通过穷举所有可能情况来找到所有解的算法.如果一个候选解最后被发现并不是可行解,回溯算法会 ...
- 高级数据结构与算法 | 深度遍历搜索(DFS)与广度遍历搜索(BFS)
文章目录 深度优先搜索(DFS) 员工的重要性 图像渲染 岛屿的周长 被围绕的区域 岛屿数量 岛屿的最大面积 广度优先搜索(BFS) N叉树的层序遍历 腐烂的橘子 单词接龙 最小基因变化 打开转盘锁 ...
最新文章
- vue前端表格插件_Grid.js - 跨框架的前端表格插件
- Pycharm快捷键及一些常用设置
- A Learned Representation for Artistic Style论文理解
- 谷歌大脑发布神经网络的「核磁共振」,并公开相关代码
- 通过Shell开发企业级专业服务启动脚本案例(MySQL)
- python常用英语单词-python – 获取英语单词的基本形式
- Hive的下载安装,以及配置mysql作为元数据库
- SqliteHelper整理
- BZOJ2115XOR——线性基
- Module database cache not built yet, using slow search
- 上手测试GaussDB(for Redis) 和开源 Redis,只为推荐质优价廉的Redis
- c++list遍历_小白学PyTorch | 6 模型的构建访问遍历存储(附代码)
- NAT对数据业务的影响
- json字符串-单、双引号
- SCI 论文免费下载地址
- 车辆管理设备V系列JTT-808协议简介
- 算法篇-用栈来求解汉诺塔问题
- 查询计算机上可用端口的两种方法
- Android静态安全检查(九):不安全的SDCard存储检测
- 机器视觉中的像素、分辨率、灰度值等概念
热门文章
- 封装案例-完成开火方法
- Spring Boot整合Spring Data Redis-存取Java对象
- mysql 交集_MYSQL交集函数
- 摩根斯坦利面试题库_经验 | 金融公司摩根士丹利从笔试到实习的全程经验
- 210314阶段三VS使用Linux 的sqlite3 API
- 210130阶段三socket服务器
- Java获得时间 String与Timestamp互转
- POJ3070 矩阵快速幂模板
- Java 8 中的工厂方法模式
- CodeForces - 1486C2 Guessing the Greatest (hard version)(二分+交互)