C++实现LRU-LFU混合淘汰算法 _ 频率统计与时间戳结合【源码】

风明吖_4201

风明吖_4201

2026-04-14

751人浏览

原创

不能直接用 std::list + std::unordered_map 实现 LFU/LRU 混合淘汰,因二者排序维度冲突:LRU 依赖访问时间,LFU 依赖频次,单链表无法同时维护两个有序性;必须为每个频次维护独立 LRU 链表(频次桶),并记录 min_freq,Node 需含 key、value、freq,哈希表存 key→list::iterator,更新频次时需从旧桶删除再插入新桶头部,淘汰时从 min_freq 桶尾部删除并维护 min_freq 正确性。

c++实现lru-lfu混合淘汰算法 _ 频率统计与时间戳结合【源码】

为什么不能直接用 std::list + std::unordered_map 实现混合淘汰

因为 LRU 和 LFU 的排序维度冲突:LRU 依赖访问时间(最新访问在前),LFU 依赖频次(高频在前)。单纯链表只能维护一个序,强行插入时会破坏另一个维度的局部性。比如按频率插入后,同一频率内的节点时间顺序就乱了;反之亦然。

常见错误是把 std::list 当成“双排序容器”来用,结果 get() 后频次+1,却只移动节点到新频次段头部,没保证该段内仍按 LRU 排序——这会导致同频次下最久未用的项反而被保留。

  • 必须为每个频次维护独立的 LRU 链表(即“频次桶”)
  • 需额外记录全局最小频次(min_freq),避免每次淘汰都遍历所有桶找非空桶
  • std::unordered_map 的 value 类型不能只存 value,必须包含 freq 和指向其所在链表节点的迭代器(list::iterator)

Node 结构体里必须带 key 字段

淘汰时要从链表尾部删节点,但仅靠 value 无法反查 key,也就无法从哈希表中同步删除映射。所以 Node 至少得有:key、value、freq。

有人尝试用 std::map>> 省掉 Node,但这样无法在 O(1) 内通过 value 定位到对应链表节点——因为 list::remove() 需迭代器,而 map 查 value 是 O(N)。

  • 正确做法:struct Node { K key; V value; int freq; };
  • 哈希表存:unordered_map::iterator>> → 不行,pair 无法存 freq
  • 应存:unordered_map::iterator>,再配合外部结构体或封装类管理 freq 和桶映射

更新频次时如何保持同频次内 LRU 有序

关键不是“把节点移到新频次链表头”,而是“从旧频次链表中移除,再插入新频次链表头”。否则旧链表残留的迭代器会失效,且新链表头插后,同频次内访问顺序就丢失了。

具体步骤(以 get(key) 为例):

C++14
C++14

C++14 对 C++11 的修正与增强版本,适合旧系统维护和较老工具链兼容。

下载
  • 从哈希表拿到 it = cache[key](指向 list::iterator)
  • 读出当前 it->freq,记为 f
  • 从 freq_buckets[f] 中 erase(it)
  • it->freq++
  • 将 it 插入 freq_buckets[f+1] 头部(push_front)
  • 若 freq_buckets[f] 变为空,且 f == min_freq,则 min_freq++

注意:list::erase(iterator) 不会使其他迭代器失效,但必须确保 it 确实属于 freq_buckets[f] —— 这要求每次插入/移动都严格更新桶引用。

淘汰策略触发时机与 min_freq 维护陷阱

不是每次 put() 溢出才淘汰,而是当 size() > capacity 且新 key 不存在时,才触发淘汰。此时必须从 freq_buckets[min_freq] 尾部删节点。

容易错的地方:

  • min_freq 初始值设为 0,但首次插入后应为 1;更稳妥是初始化为 1,第一次 put 后显式设 min_freq = 1
  • 淘汰后如果 freq_buckets[min_freq] 变空,要循环 min_freq++ 直到非空或越界 —— 但实际只需检查一次,因为只有刚删完的那个频次可能为空,其他频次不会因本次操作变空
  • 如果 put 是更新已有 key(key 存在),不触发淘汰,但要更新频次和位置;如果是新 key,先淘汰再插入,且新节点 freq = 1,插入 freq_buckets[1] 头部,并重置 min_freq = 1

真正复杂的是多线程场景下的 min_freq 竞态:一个线程刚删完 min_freq 桶,另一个线程 get 升频导致该桶再次非空,但 min_freq 已被跳过。单线程实现可忽略,但生产环境必须加锁或改用无锁计数器。

C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!

相关文章

PHP速学视频免费教程(入门到精通)
PHP速学视频免费教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

c++ c++时间戳

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
golang结构体相关大全
golang结构体相关大全

本专题整合了golang结构体相关大全,想了解更多内容,请阅读专题下面的文章。

2025.06.09

5274

18

golang结构体方法
golang结构体方法

本专题整合了golang结构体相关内容,请阅读专题下面的文章了解更多。

2025.07.04

5451

25

string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.02

7259

3

int占多少字节
int占多少字节

int占4个字节,意味着一个int变量可以存储范围在-2,147,483,648到2,147,483,647之间的整数值,在某些情况下也可能是2个字节或8个字节,int是一种常用的数据类型,用于表示整数,需要根据具体情况选择合适的数据类型,以确保程序的正确性和性能。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2024.08.29

3465

6

c++怎么把double转成int
c++怎么把double转成int

本专题整合了 c++ double相关教程,阅读专题下面的文章了解更多详细内容。

2025.08.29

4548

10

C++中int的含义
C++中int的含义

本专题整合了C++中int相关内容,阅读专题下面的文章了解更多详细内容。

2025.08.29

3045

10

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

2023.08.10

4718

6

Python 多线程与异步编程实战
Python 多线程与异步编程实战

本专题系统讲解 Python 多线程与异步编程的核心概念与实战技巧,包括 threading 模块基础、线程同步机制、GIL 原理、asyncio 异步任务管理、协程与事件循环、任务调度与异常处理。通过实战示例,帮助学习者掌握 如何构建高性能、多任务并发的 Python 应用。

2025.12.24

1309

20

java多线程相关教程合集
java多线程相关教程合集

本专题整合了java多线程相关教程,阅读专题下面的文章了解更多详细内容。

2026.01.21

443

30

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
C++ Core Guidelines
C++ Core Guidelines

共0课时 | 0人学习

C++ Reference
C++ Reference

共0课时 | 0人学习

C++ 官方标准说明
C++ 官方标准说明

共0课时 | 0人学习