LFU算法解析

文章目录

  • LFU缓存中关键变量的访问与更新机制
    • 1. `min_freq` - 最小频率
      • 访问时机
      • 更新时机
      • 更新示例
    • 2. `capacity` - 缓存容量
      • 访问时机
      • 更新时机
      • 访问示例
    • 3. `key_to_node` - 键到节点的映射
      • 访问时机
      • 更新时机
      • 更新示例
    • 4. `freq_to_dummy` - 频率到链表哑节点的映射
      • 访问时机
      • 更新时机
      • 更新示例
    • 变量访问的并发安全考虑
    • 变量操作时序图
    • 参考实现
      • C++实现
      • Go线程安全实现
    • 参考

LFU缓存中关键变量的访问与更新机制

LFU缓存结构图

1. min_freq - 最小频率

访问时机

  • put方法中处理缓存已满情况时,用于确定哪个频率链表中的节点应被淘汰
  • get_node方法中,需要决定是否更新最小频率时

更新时机

  • 初始化:在构造函数中初始化为0(或1,取决于实现)
  • 增加:在get_node方法中,当一个频率为min_freq的节点的频率增加后,如果该频率链表变为空,则min_freq++
  • 重置:在put方法中添加新节点时,将min_freq设为1,因为新节点的初始频率为1

更新示例

// get_node方法中,当频率为min_freq的列表变空时
if (dummy->prev == dummy) { // 链表为空freq_to_dummy.erase(node->freq);delete dummy;if (min_freq == node->freq) {min_freq++; // 增加min_freq}
}// put方法中,添加新节点时
key_to_node[key] = node = new Node(key, val);
push_front(1, node);
min_freq = 1; // 重置min_freq

2. capacity - 缓存容量

访问时机

  • put方法中,决定是否需要淘汰节点前检查当前节点数是否达到容量
  • getput方法开始时,检查容量是否大于0

更新时机

  • 初始化:仅在构造函数中设置,之后不再更改
  • 该变量通常是只读的,缓存容量在创建后不会改变

访问示例

// 检查容量是否为正数
if (capacity <= 0) return -1; // 或 return;// 检查是否达到容量上限
if (key_to_node.size() == capacity) {// 执行淘汰逻辑
}

3. key_to_node - 键到节点的映射

访问时机

  • get_node方法中,查找指定key对应的节点
  • put方法中,检查key是否已存在
  • put方法中淘汰节点时,从映射中移除被淘汰节点的key

更新时机

  • 插入:在put方法中添加新节点时
  • 删除:在put方法中淘汰节点时
  • 注意get方法只读取不修改此映射

更新示例

// 查找节点
auto it = key_to_node.find(key);
if (it == key_to_node.end()) {return nullptr;
}
Node* node = it->second;// 添加新节点
key_to_node[key] = node = new Node(key, val);// 删除节点
key_to_node.erase(back_node->key);

4. freq_to_dummy - 频率到链表哑节点的映射

访问时机

  • get_node方法中,获取节点当前频率对应的链表头
  • push_front方法中,获取指定频率的链表头
  • put方法中淘汰节点时,获取min_freq对应的链表头

更新时机

  • 插入:在push_front方法中发现指定频率的链表不存在时,创建新链表
  • 删除:在get_node方法中,当频率链表变为空时,删除该频率的映射
  • 删除:在put方法淘汰节点后,如果频率链表变为空,删除该频率的映射

更新示例

// 创建新频率链表
auto it = freq_to_dummy.find(freq);
if (it == freq_to_dummy.end()) {it = freq_to_dummy.emplace(freq, new_list()).first;
}// 删除空链表的映射
if (dummy->prev == dummy) { // 链表为空freq_to_dummy.erase(node->freq);delete dummy;
}

变量访问的并发安全考虑

在多线程环境中,这些变量的访问和更新需要特别注意:

  1. 锁的粒度

    • 在Go实现中,我们使用单个读写锁保护所有变量
    • 对于高性能需求,可以考虑更细粒度的锁策略
  2. 读写分离

    • get操作主要是读操作,但会修改节点频率和链表结构
    • put操作既读又写
    • 可以使用读写锁提高并发性能
  3. 原子性

    • 多个变量的更新需要保持原子性和一致性
    • 例如,从一个频率链表移动到另一个频率链表的过程必须是原子的

变量操作时序图

LFU缓存操作时序图

【GET操作】
1. 访问 key_to_node 查找节点
2. 如果找到节点:a. 从原频率链表移除节点b. 访问并可能更新 freq_to_dummyc. 检查并可能更新 min_freqd. 增加节点频率并添加到新频率链表e. 返回节点值【PUT操作 - 更新已有节点】
1. 访问 key_to_node 查找节点
2. 如果找到节点:a. 执行类似GET的频率更新操作b. 更新节点值【PUT操作 - 添加新节点,缓存已满】
1. 检查 capacity 与 key_to_node.size()
2. 访问 min_freq 确定要淘汰的频率
3. 访问 freq_to_dummy[min_freq] 获取链表头
4. 移除链表尾部节点
5. 更新 key_to_node 删除被淘汰节点
6. 可能更新 freq_to_dummy 删除空链表
7. 创建新节点并添加到 key_to_node
8. 更新 freq_to_dummy 确保频率1的链表存在
9. 将新节点添加到频率1的链表
10. 重置 min_freq = 1

参考实现

C++实现

class Node {
public:int key;int value;int freq = 1;Node* prev;Node* next;Node(int k = 0,int v = 0):key(k),value(v){}
};class LFUCache {
private:int min_freq;int capacity;unordered_map<int,Node*> key_to_node;unordered_map<int,Node*> freq_to_dummy;Node* get_node(int key) {auto it = key_to_node.find(key);if(it==key_to_node.end()) {return nullptr;}Node* node = it->second;remove(node);Node* dummy = freq_to_dummy[node->freq];if(dummy->prev == dummy) {freq_to_dummy.erase(node->freq);delete dummy;if(min_freq==node->freq) {min_freq++;}}push_front(++node->freq,node);return node;}// 创建一个新的双向链表Node* new_list() {Node* dummy = new Node();dummy->next = dummy;dummy->prev = dummy;return dummy;}// 在链表头添加一个节点(把一本书放在最上面)void push_front(int freq, Node *x) {auto it = freq_to_dummy.find(freq);if (it==freq_to_dummy.end()) {it = freq_to_dummy.emplace(freq,new_list()).first;}Node* dummy = it->second;x->prev = dummy;x->next = dummy->next;x->prev->next = x;x->next->prev = x;}// 删除一个节点(抽出一本书)void remove(Node *x) {x->prev->next = x->next;x->next->prev = x->prev;}public:LFUCache(int capacity) : capacity(capacity), min_freq(0) {}int get(int key) {if (capacity <= 0) return -1;Node* node = get_node(key);return node ? node->value : -1;}void put(int key, int val) {if (capacity <= 0) return;Node* node = get_node(key);if(node) {node->value = val;return;}if(key_to_node.size() == capacity) {Node* dummy = freq_to_dummy[min_freq];Node* back_node = dummy->prev;key_to_node.erase(back_node->key);remove(back_node);delete back_node;if(dummy->prev == dummy) {freq_to_dummy.erase(min_freq);delete dummy;}}node = new Node(key, val);key_to_node[key] = node;push_front(1, node);min_freq = 1;}
};

Go线程安全实现

package cacheimport ("sync"
)// Node 表示缓存中的一个节点
type Node struct {key   intvalue intfreq  intprev  *Nodenext  *Node
}// LFUCache 是一个并发安全的LFU缓存实现
type LFUCache struct {capacity    intminFreq     intkeyToNode   map[int]*NodefreqToDummy map[int]*Nodemutex       sync.RWMutex // 读写锁用于并发控制
}// NewLFUCache 创建一个新的LFU缓存
func NewLFUCache(capacity int) *LFUCache {return &LFUCache{capacity:    capacity,minFreq:     0,keyToNode:   make(map[int]*Node),freqToDummy: make(map[int]*Node),}
}// newList 创建一个新的双向链表并返回哑节点
func (c *LFUCache) newList() *Node {dummy := &Node{}dummy.prev = dummydummy.next = dummyreturn dummy
}// pushFront 在链表头部添加一个节点
func (c *LFUCache) pushFront(freq int, node *Node) {dummy, ok := c.freqToDummy[freq]if !ok {dummy = c.newList()c.freqToDummy[freq] = dummy}node.prev = dummynode.next = dummy.nextnode.prev.next = nodenode.next.prev = node
}// remove 从链表中移除一个节点
func (c *LFUCache) remove(node *Node) {node.prev.next = node.nextnode.next.prev = node.prev
}// getNode 获取并更新节点的频率
func (c *LFUCache) getNode(key int) *Node {node, ok := c.keyToNode[key]if !ok {return nil}// 从当前频率列表中移除节点c.remove(node)// 检查并处理空链表dummy := c.freqToDummy[node.freq]if dummy.prev == dummy { // 链表为空delete(c.freqToDummy, node.freq)// 更新最小频率if c.minFreq == node.freq {c.minFreq++}}// 增加节点频率并添加到新频率列表node.freq++c.pushFront(node.freq, node)return node
}// Get 获取缓存中的值
func (c *LFUCache) Get(key int) int {if c.capacity <= 0 {return -1}c.mutex.Lock()defer c.mutex.Unlock()node := c.getNode(key)if node == nil {return -1}return node.value
}// Put 设置缓存中的值
func (c *LFUCache) Put(key, value int) {if c.capacity <= 0 {return}c.mutex.Lock()defer c.mutex.Unlock()// 如果key已存在,更新值if node := c.getNode(key); node != nil {node.value = valuereturn}// 如果缓存已满,移除LFU项if len(c.keyToNode) >= c.capacity {dummy := c.freqToDummy[c.minFreq]backNode := dummy.prev// 从映射和链表中移除delete(c.keyToNode, backNode.key)c.remove(backNode)// 如果链表为空,从freq映射移除if dummy.prev == dummy {delete(c.freqToDummy, c.minFreq)}}// 创建新节点并添加到频率为1的链表node := &Node{key:   key,value: value,freq:  1,}c.keyToNode[key] = nodec.pushFront(1, node)c.minFreq = 1
}// Size 返回当前缓存中的元素数量
func (c *LFUCache) Size() int {c.mutex.RLock()defer c.mutex.RUnlock()return len(c.keyToNode)
}// Clear 清空缓存
func (c *LFUCache) Clear() {c.mutex.Lock()defer c.mutex.Unlock()c.keyToNode = make(map[int]*Node)c.freqToDummy = make(map[int]*Node)c.minFreq = 0
}

参考

  1. 灵神题解

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/79519.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ByteArrayInputStream 类详解

ByteArrayInputStream 类详解 ByteArrayInputStream 是 Java 中用于从字节数组读取数据的输入流&#xff0c;位于 java.io 包。它允许将内存中的字节数组当作输入流来读取&#xff0c;是处理内存数据的常用工具。 1. 核心特性 内存数据源&#xff1a;从字节数组&#xff08;b…

rvalue引用()

一、先确定基础:左值(Lvalue)和右值(Rvalue) 理解Rvalue引用,首先得搞清楚左值和右值的概念。 左值(Lvalue):有明确内存地址的表达式,可以取地址。比如变量名、引用等。 复制代码 int a = 10; // a是左值 int& ref = a; // ref也是左值右值(Rval…

吴恩达深度学习作业 RNN模型——字母级语言模型

一. 简单复习一下RNN RNN RNN适用于处理序列数据&#xff0c;令是序列的第i个元素&#xff0c;那么就是一个长度为的序列&#xff0c;NLP中最常见的元素是单词&#xff0c;对应的序列是句子。 RNN使用同一个神经网络处理序列中的每一个元素。同时&#xff0c;为了表示序列的…

基于python的哈希查表搜索特定文件

Python有hashlib库&#xff0c;支持多种哈希算法&#xff0c;比如MD5、SHA1、SHA256等。通常SHA256比较安全&#xff0c;但MD5更快&#xff0c;但可能存在碰撞风险&#xff0c;得根据自己需求决定。下面以SHA256做例。 import hashlib import os from typing import Dict, Lis…

idea创建springboot项目无法创建jdk8原因及多种解决方案

idea创建springboot项目无法创建jdk8原因及多种解决方案 提示&#xff1a;帮帮志会陆续更新非常多的IT技术知识&#xff0c;希望分享的内容对您有用。本章分享的是springboot的使用。前后每一小节的内容是存在的有&#xff1a;学习and理解的关联性。【帮帮志系列文章】&#x…

【C++进阶十】多态深度剖析

【C进阶十】多态深度剖析 1.多态的概念及条件2.虚函数的重写3.重写、重定义、重载区别4.C11新增的override 和final5.抽象类6.虚表指针和虚表6.1什么是虚表指针6.2指向谁调用谁&#xff0c;传父类调用父类&#xff0c;传子类调用子类 7.多态的原理8.单继承的虚表状态9.多继承的…

面向网络安全的开源 大模型-Foundation-Sec-8B

1. Foundation-Sec-8B 整体介绍 Foundation-Sec-8B 是一个专注于网络安全领域的大型语言模型 (LLM),由思科的基础人工智能团队 (Foundation AI) 开发 。它基于 Llama 3.1-8B 架构构建,并通过在一个精心策划和整理的网络安全专业语料库上进行持续预训练而得到增强 。该模型旨在…

Python爬虫的基础用法

Python爬虫的基础用法 python爬虫一般通过第三方库进行完成 导入第三方库&#xff08;如import requests &#xff09; requests用于处理http协议请求的第三方库,用python解释器中查看是否有这个库&#xff0c;没有点击安装获取网站url&#xff08;url一定要解析正确&#xf…

WHAT - Tailwind CSS + Antd = MetisUI组件库

文章目录 Tailwind 和 Antd 组件库MetisUI 组件库 Tailwind 和 Antd 组件库 在 WHAT - Tailwind 样式方案&#xff08;不写任何自定义样式&#xff09; 中我们介绍了 Tailwind&#xff0c;至于 Antd 组件库&#xff0c;我们应该都耳熟能详&#xff0c;官网地址&#xff1a;htt…

Day 4:牛客周赛Round 91

好久没写了&#xff0c;问题还蛮多的。听说这次是苯环哥哥出题 F题 小苯的因子查询 思路 考虑求因子个数&#xff0c;用质因数分解&#xff1b;奇数因子只需要去掉质数为2的情况&#xff0c;用除法。 这里有个比较妙的细节是&#xff0c;提前处理出数字x的最小质因数&#xff0…

使用直觉理解不等式

问题是这个&#xff1a; 题目 探究 ∣ max ⁡ b { q 1 ( z , b ) } − max ⁡ b { q 2 ( z , b ) } ∣ ≤ max ⁡ b ∣ q 1 ( z , b ) − q 2 ( z , b ) ∣ |\max_b\{q_1(z,b)\}-\max_b\{q_2(z,b)\}|\le\max_b|q_1(z,b)-q_2(z,b)| ∣maxb​{q1​(z,b)}−maxb​{q2​(z,b)}∣≤…

恶心的win11更新DIY 设置win11更新为100年

‌打开注册表编辑器‌&#xff1a;按下Win R键&#xff0c;输入regedit&#xff0c;然后按回车打开注册表编辑器。‌12‌导航到指定路径‌&#xff1a;在注册表编辑器中&#xff0c;依次展开HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings‌新建DWORD值‌&…

嵌入式驱动学习

时钟 定义 周期型的0、1信号 时钟信号由“心脏”时钟源产生&#xff0c;通过“动脉”时钟树传播到整个芯片中。 SYSCLK系统时钟&#xff0c;由HSI、HSE、PLLCLK三选一。 HCLK是AHB总线时钟&#xff0c; PCLK是APB总线时钟。 使用某个外设&#xff0c;必须要先使能该外设时钟系统…

Java:从入门到精通,你的编程之旅

Java&#xff0c;一门历久弥新的编程语言&#xff0c;自诞生以来就以其跨平台性、面向对象、稳定性和安全性等特性&#xff0c;在企业级应用开发领域占据着举足轻重的地位。无论你是初学者还是经验丰富的开发者&#xff0c;Java 都能为你提供强大的工具和广阔的舞台。 为什么选…

Linux:深入理解数据链路层

实际上一台主机中&#xff0c;报文并没有通过网络层直接发送出去&#xff0c;而是交给了自己的下一层协议——数据链路层&#xff01;&#xff01; 一、理解数据链路层 网络层交付给链路层之前&#xff0c;会先做决策再行动&#xff08;会先查一下路由表&#xff0c;看看目标网…

Python基本语法(类和实例)

类和实例 类和对象是面向对象编程的两个主要方面。类创建一个新类型&#xff0c;而对象是这个 类的实例&#xff0c;类使用class关键字创建。类的域和方法被列在一个缩进块中&#xff0c;一般函数 也可以被叫作方法。 &#xff08;1&#xff09;类的变量&#xff1a;甴一个类…

2025 年如何使用 Pycharm、Vscode 进行树莓派 Respberry Pi Pico 编程开发详细教程(更新中)

micropython 概述 micropython 官方网站&#xff1a;https://www.micropython.org/ 安装 Micropython 支持固件 树莓派 Pico 安装 Micropython 支持固件 下载地址&#xff1a;https://www.raspberrypi.com/documentation/microcontrollers/ 选择 MicroPython 下载 RPI_PIC…

flink rocksdb状态说明

文章目录 1.默认情况2.flink中的状态3.RocksDB4.对比情况5.使用6.RocksDB架构7.参考文章8.总结提示:以下主要考虑flink 状态永久存储 rocksdb情况,做一些简单说明 1.默认情况 当flink使用rocksdb存储状态时。无论是永久存储还是临时存储都可能会落盘写文件(如果没有配置存储…

安装SDL和FFmpeg

1、先记录SDL 这玩意还是有一点讲究的 具体步骤&#xff1a; 下载 SDL包&#xff1a; 链接&#xff1a;https://www.libsdl.org/release/SDL2-2.0.14.tar.gz 可以用迅雷&#xff0c;下载完之后&#xff0c; 解压&#xff1a; tar -zxvf SDL2-2.0.14.tar.gz进入安装目录 cd …

2022年408真题及答案

2022年计算机408真题 2022年计算机408答案 2022 408真题下载链接 2022 408答案下载链接