LRU、LFU 内存淘汰算法的设计与实现

1、背景介绍

LRU、LFU都是内存管理淘汰算法,内存管理是计算机技术中重要的一环,也是多数操作系统中必备的模块。应用场景:假设 给定你一定内存空间,需要你维护一些缓存数据,LRU、LFU就是在内存已经满了的情况下,如果再次添加新的数据,该淘汰哪些数据来留出新数据的内存空间???

2、LRU(least recently used)

LRU(east recently used),即最近最少使用 ,也就是说 在内存满的情况下,将会淘汰很久都没有使用过的数据。例如 leetcode 146题。

需求:现在需要设计一个算法,使得插入新数据、获取已经存入的数据,使得平均时间复杂度O(1)。

设计思路:因为插入、获取数据时,都会更新时间戳,还需要使得平均时间复杂度O(1),可以使用双链表+哈希表的方式来存储。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如上图,哈希表里存储 键与双链表节点,双链表的head表示最近使用过的数据,tail表示很久都没使用过的数据。

  • 插入数据时,在哈希表建立key与Node节点,然后把Node节点插入双链表的头部位置。

  • 获取数据时,从哈希表拿到Node节点,然后把Node节点从双链表中分离出来,插入双链表的头部位置即可。

    在这里插入图片描述

以上两个步骤,时间复杂度都是O(1)。所以符合题意。

代码如下:

// lc146题 直接能过的代码
class LRUCache {// LRU,最近最少使用// 用双链表节点包装进行连接private HashMap<Integer, Node> map; // 哈希表,存储key与双链表节点private int maxSize; // 缓存的最大容量private Node head, tail; // 头部是最近使用的,尾部 是很久没使用的private static class Node { // 双链表节点int key, val;Node left, right;public Node(int k, int v) {key = k;val = v;}}public LRUCache(int capacity) {map = new HashMap<>();maxSize = capacity;}public int get(int key) {if(!map.containsKey(key)) return -1;Node node = map.get(key);if (node == head) return node.val;apart(node); //分离出node节点node.right = head; // 插入到双链表的头部位置head.left = node;head = node;return node.val;}public void put(int key, int value) {if (!map.containsKey(key)) {Node node = new Node(key, value);node.right = head;if (head != null) head.left = node;if (tail == null) tail = node;head = node;map.put(key, node);if (map.size() > maxSize) { // 容量超了,删除双链表的尾部元素if (tail.left != null) {tail.left.right = null;}map.remove(tail.key);Node pre = tail.left;tail.left = null;tail = pre;}} else {Node node = map.get(key);node.val = value;get(key); // 调用get,使其向前移动}}// node的左右两边连接,将node抽离出private void apart(Node node) {node.left.right = node.right;if (node.right != null) {node.right.left = node.left;}if (node == tail) {tail = node.left;}node.left = node.right = null;}
}/*** Your LRUCache object will be instantiated and called as such:* LRUCache obj = new LRUCache(capacity);* int param_1 = obj.get(key);* obj.put(key,value);*/

3、LFU(least frequancy used)

LFU(least frequancy used), 即不常用算法,按照每个数据的访问次数来判断数据的使用情况。如果一个数据在近一段时间内没有被访问或者被访问的可能性小,则会被淘汰。(简单点说,就是按照“使用频率”来分级的)例题:leetcode 460题

需求:现在需要设计一个算法,使得插入、获取数据的平均时间复杂度O(1)。

设计思路:按照使用频率进行划分,相同频率的数据放在同一个“桶”内,从左往右频率逐渐升高;而桶内部是从上往下,按照插入桶内的时间来排序,新插入的节点在桶的顶部,很久之前插入的节点在桶的底部,如下图所示:

在这里插入图片描述

注意:当内存满了的时候,会删除 频率最低的桶内,最后的一个数据节点

代码实现如下:

public class LFUCache {public LFUCache(int K) {capacity = K;size = 0;records = new HashMap<>();heads = new HashMap<>();headList = null;}private int capacity; // 缓存的大小限制,即Kprivate int size; // 缓存目前有多少个节点private HashMap<Integer, Node> records;// 表示key(Integer)由哪个节点(Node)代表private HashMap<Node, NodeList> heads; // 表示节点(Node)在哪个桶(NodeList)里private NodeList headList; // 整个结构中位于最左的桶// 节点的数据结构public static class Node {public Integer key;public Integer value;public Integer times; // 这个节点发生get或者set的次数总和public Node up; // 节点之间是双向链表所以有上一个节点public Node down;// 节点之间是双向链表所以有下一个节点public Node(int k, int v, int t) {key = k;value = v;times = t;}}// 桶结构public static class NodeList {public Node head; // 桶的头节点public Node tail; // 桶的尾节点public NodeList last; // 桶之间是双向链表所以有前一个桶public NodeList next; // 桶之间是双向链表所以有后一个桶public NodeList(Node node) {head = node;tail = node;}// 把一个新的节点加入这个桶,新的节点都放在顶端变成新的头部public void addNodeFromHead(Node newHead) {newHead.down = head;head.up = newHead;head = newHead;}// 判断这个桶是不是空的public boolean isEmpty() {return head == null;}// 删除node节点并保证node的上下环境重新连接public void deleteNode(Node node) {if (head == tail) {head = null;tail = null;} else {if (node == head) {head = node.down;head.up = null;} else if (node == tail) {tail = node.up;tail.down = null;} else {node.up.down = node.down;node.down.up = node.up;}}node.up = null;node.down = null;}}// removeNodeList:刚刚减少了一个节点的桶// 这个函数的功能是,判断刚刚减少了一个节点的桶是不是已经空了。// 1)如果不空,什么也不做//// 2)如果空了,removeNodeList还是整个缓存结构最左的桶(headList)。// 删掉这个桶的同时也要让最左的桶变成removeNodeList的下一个。//// 3)如果空了,removeNodeList不是整个缓存结构最左的桶(headList)。// 把这个桶删除,并保证上一个的桶和下一个桶之间还是双向链表的连接方式//// 函数的返回值表示刚刚减少了一个节点的桶是不是已经空了,空了返回true;不空返回falseprivate boolean modifyHeadList(NodeList removeNodeList) {if (removeNodeList.isEmpty()) {if (headList == removeNodeList) {headList = removeNodeList.next;if (headList != null) {headList.last = null;}} else {removeNodeList.last.next = removeNodeList.next;if (removeNodeList.next != null) {removeNodeList.next.last = removeNodeList.last;}}return true;}return false;}// 函数的功能// node这个节点的次数+1了,这个节点原来在oldNodeList里。// 把node从oldNodeList删掉,然后放到次数+1的桶中// 整个过程既要保证桶之间仍然是双向链表,也要保证节点之间仍然是双向链表private void move(Node node, NodeList oldNodeList) {oldNodeList.deleteNode(node);// preList表示次数+1的桶的前一个桶是谁// 如果oldNodeList删掉node之后还有节点,oldNodeList就是次数+1的桶的前一个桶// 如果oldNodeList删掉node之后空了,oldNodeList是需要删除的,所以次数+1的桶的前一个桶,是oldNodeList的前一个NodeList preList = modifyHeadList(oldNodeList) ? oldNodeList.last : oldNodeList;// nextList表示次数+1的桶的后一个桶是谁NodeList nextList = oldNodeList.next;if (nextList == null) {NodeList newList = new NodeList(node);if (preList != null) {preList.next = newList;}newList.last = preList;if (headList == null) {headList = newList;}heads.put(node, newList);} else {if (nextList.head.times.equals(node.times)) {nextList.addNodeFromHead(node);heads.put(node, nextList);} else {NodeList newList = new NodeList(node);if (preList != null) {preList.next = newList;}newList.last = preList;newList.next = nextList;nextList.last = newList;if (headList == nextList) {headList = newList;}heads.put(node, newList);}}}public void put(int key, int value) {if (capacity == 0) {return;}if (records.containsKey(key)) {Node node = records.get(key);node.value = value;node.times++;NodeList curNodeList = heads.get(node);move(node, curNodeList);} else {if (size == capacity) {Node node = headList.tail;headList.deleteNode(node);modifyHeadList(headList);records.remove(node.key);heads.remove(node);size--;}Node node = new Node(key, value, 1);if (headList == null) {headList = new NodeList(node);} else {if (headList.head.times.equals(node.times)) {headList.addNodeFromHead(node);} else {NodeList newList = new NodeList(node);newList.next = headList;headList.last = newList;headList = newList;}}records.put(key, node);heads.put(node, headList);size++;}}public int get(int key) {if (!records.containsKey(key)) {return -1;}Node node = records.get(key);node.times++;NodeList curNodeList = heads.get(node);move(node, curNodeList);return node.value;}
}

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

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

相关文章

以容器方式运行 windows 图形化界面系统,附docker详细配置步骤和yaml完整执行文件

以容器方式运行 windows 图形化界面系统,附docker详细配置步骤和yaml完整执行文件。 常规普通的docker中运行windows系统,只能运行无界面化的系统,例如: 要在Docker中运行Windows应用程序,需要使用Windows容器。以下是一些步骤: 确认您的操作系统支持Docker桌面应用程…

2023-2024年最新大数据学习路线

文章目录 2023-2024年最新大数据学习路线大数据开发入门*01*阶段案例实战 大数据核心基础*02*阶段案例实战 千亿级数仓技术*03*阶段项目实战 PB级内存计算04阶段项目实战 亚秒级实时计算*05*阶段项目实战 大厂面试*06* 2023-2024年最新大数据学习路线 新路线图在Spark一章不再…

Android跨进程通信:Binder机制原理

目录 1. Binder到底是什么&#xff1f; 2. 知识储备 2.1 进程空间划分 2.2 进程隔离 & 跨进程通信&#xff08; IPC &#xff09; 2.3 内存映射 2.3.1 作用 2.3.2 实现过程 2.3.3 特点 2.3.4 应用场景 2.3.5 实例讲解 ① 文件读 / 写操作 ② 跨进程通信 3. Bi…

【学习笔记】Prufer序列

Prufer序列 起源于对 C a y l e y Cayley Cayley定理的证明&#xff0c;但是其功能远不止于此 现在考虑将一棵n个节点的树与一个长度为n-2的prufer序列构造对应关系 T r e e − > P r u f e r : Tree->Prufer: Tree−>Prufer: ①从树上选择编号最小的叶子节点&#x…

Cron表达式_用于定时调度任务

一、Cron表达式简介 Cron表达式是一个用于设置计划任务的字符串&#xff0c;该字符串以5或6个空格分隔&#xff0c;分为6或7个域&#xff0c;每一个域代表任务在相应时间、日期或时间间隔执行的规则【Cron表达式最初是在类Unix操作中系统中使用的&#xff0c;但现在已经广泛应用…

人机融合需要在事实与价值之间构建新型的拓扑关系

人机融合&#xff0c;这是指将人类智慧&#xff08;含艺术&#xff09;与计算机科技相结合&#xff0c;共同解决复杂问题的一种新方式。在人机融合中&#xff0c;我们需要构建事实与价值之间的新型拓扑关系&#xff0c;以实现更有效的知识管理和决策支持。 事实是指客观存在的、…

Python爬虫爬取豆瓣电影短评(爬虫入门,Scrapy框架,Xpath解析网站,jieba分词)

声明&#xff1a;以下内容仅供学习参考&#xff0c;禁止用于任何商业用途 很久之前就想学爬虫了&#xff0c;但是一直没机会&#xff0c;这次终于有机会了 主要参考了《疯狂python讲义》的最后一章 首先安装Scrapy&#xff1a; pip install scrapy 然后创建爬虫项目&#…

EdgeMoE: Fast On-Device Inference of MoE-based Large Language Models

本文是LLM系列文章&#xff0c;针对《EdgeMoE: Fast On-Device Inference of MoE-based Large Language Models》的翻译。 EdgeMoE&#xff1a;基于MoE的大型语言模型的快速设备推理 摘要1 引言2 实验与分析3 EDGEMOE设计4 评估5 相关工作6 结论 摘要 GPT和LLaMa等大型语言模…

力扣26:删除有序数组中的重复项

26. 删除有序数组中的重复项 - 力扣&#xff08;LeetCode&#xff09; 题目&#xff1a; 给你一个 非严格递增排列 的数组 nums &#xff0c;请你 原地 删除重复出现的元素&#xff0c;使每个元素 只出现一次 &#xff0c;返回删除后数组的新长度。元素的 相对顺序 应该保持 …

关于业务库从MySQL迁移到DM8的操作指南

升级前准备 jdbc:dm://10.252.10.15:5237 username: datashare password: datashare123 把当前MySQL数据库下的数据库表以及数据迁移到DM8。通过达梦8自带的工具可以实现迁移&#xff08;仅支持Win&#xff09; DM8管理工具下载&#xff1a;https://www.dameng.com/DM8.html…

Leetcode 95. 不同的二叉搜索树 II

文章目录 题目代码&#xff08;9.21 首刷看解析&#xff09; 题目 Leetcode 95. 不同的二叉搜索树 II 代码&#xff08;9.21 首刷看解析&#xff09; class Solution { public:vector<TreeNode*> generateTrees(int n) {return build(1,n);}vector<TreeNode*> bu…

将本地前端工程中的npm依赖上传到Nexus

【问题背景】 用Nexus搭建了内网的依赖仓库&#xff0c;需要将前端工程中node_modules中的依赖上传到Nexus上&#xff0c;但是node_modules中的依赖已经是解压后的状态&#xff0c;如果直接机械地将其简单地打包上传到Nexus&#xff0c;那么无法通过npm install下载使用。故有…

Jenkins Job的Migrate之旅

场景 使用Jenkins 做为应用的定时任务处理&#xff0c; 在上面建立的800个左右的Job, 这个环境运行了很多年&#xff0c; 当初安装的最新版本是Jenkins 1.642.3&#xff0c; 现在因为OS需要升级等原因&#xff0c; 驻在上面的Jenkins 服务器也需要一并升级&#xff0c;在新的服…

Mock.js之Element-ui搭建首页导航与左侧菜单

&#x1f3ac; 艳艳耶✌️&#xff1a;个人主页 &#x1f525; 个人专栏 &#xff1a;《Spring与Mybatis集成整合》《springMvc使用》 ⛺️ 生活的理想&#xff0c;为了不断更新自己 ! 1、Mock.js的使用 1.1.什么是Mock.js Mock.js是一个模拟数据的生成器&#xff0c;用来帮助前…

浅谈C++|文件篇

C中的文件操作是通过使用文件流来实现的。文件流提供了对文件的输入和输出功能。下面是C文件操作的基本步骤&#xff1a; 1. 包含头文件&#xff1a;首先&#xff0c;包含 <fstream> 头文件&#xff0c;它包含了进行文件操作所需的类和函数。 2 . 进行文件读写操作&#…

9领域事件

本系列包含以下文章&#xff1a; DDD入门DDD概念大白话战略设计代码工程结构请求处理流程聚合根与资源库实体与值对象应用服务与领域服务领域事件&#xff08;本文&#xff09;CQRS 案例项目介绍 # 既然DDD是“领域”驱动&#xff0c;那么我们便不能抛开业务而只讲技术&…

Windows专业版的Docker下载、安装与启用Kubenetes、访问Kubernetes Dashboard

到Docker 官网https://www.docker.com/ 下载windows操作系统对应的docker软件安装 Docker Desktop Installer-Win.exe 2023-09版本是4.23 下载后双击安装 重启windows后&#xff0c;继续安装 接受服务继续安装 解决碰到的Docker Engine stopped 打开 控制面板》程序》启用或关…

Python 与 Qt c++ 程序共享内存,传递图片

python 代码 这里Python 使用 shared_memory QT 使用 QSharedMemory 简单协议&#xff1a; 前面4个字节是 图片with,height,0,0 后面是图片数据 import sys import struct def is_little_endian():x0x12345678y struct.pack(I,x)return y[0]0x78print(f"is_little_end…

软件测试/测试开发丨利用人工智能ChatGPT自动生成PPT

点此获取更多相关资料 简介 PPT 已经渗透到我们的日常工作中&#xff0c;无论是工作汇报、商务报告、学术演讲、培训材料都常常要求编写一个正式的 PPT&#xff0c;协助完成一次汇报或一次演讲。PPT相比于传统文本的就是有布局、图片、动画效果等&#xff0c;可以给到观众更好…

css有用的小技巧(vue2)

1、希望悬浮的时候&#xff0c;img图标的src改变 <template><div style"padding: 20px"><img src"../static/icon/1.svg"></div> </template> <style scoped>img:hover {content: url(../static/icon/2.svg);} </…