LRU缓存 数据结构设计(C++)

做LeetCode第146题LRU缓存,觉得收获不小,特此记录。

请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。 实现 LRUCache 类:

  • LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存。
  • int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
  • void put(int key, int value) 如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ,则应该逐出最久未使用的关键字。

函数 get 和 put 必须以 O(1) 的平均时间复杂度运行。

关于LRU的规则,学过操作系统的肯定都知道,此处不再赘述。

题目关键在于 get() 和 put() 必须O(1),这就使得我们选择数据结构的时候需要有些考究。

首先我们回顾一下本科学过的数据结构知识,存储这样的多个key-value对肯定是使用线性表存储,而线性表又分为顺序表(数组)和链表,由于题目中涉及到了插入和删除(逐出),且有O(1)的条件限制,所以肯定是使用链表来存储。

确定了基本数据结构,我们再来想想如何使用或改造使其满足题目需要。

首先考虑 get(),我们需要做到如果关键字key在缓存中,就能查找到关键字的值,且有O(1)的条件限制,那自然是使用哈希表,哈希表的键就是LRU缓存中的key自然不用说,关键在于 哈希表的值是什么?直观想法那就是LRU的value呗,其实不然,因为我们其实不仅要通过key查到value,还有一个隐含的操作,即需要把我们刚刚查过的这个LRU缓存节点置于最高优先级的位置上(LRU的规则),所以该哈希表的value应该是一个链表节点,我们通过key查到这个链表节点以后,就可以对其进行操作以改变其优先级,同时我们也可以直接通过node->value来取得key对应的value值,一举两得。

那到底怎么体现优先级,怎么做到关键字的插入和逐出呢,其实很简单,在链表头部的表示优先级最高(即最近使用),在链表尾部的表示优先级最低(即最久未使用),这样一来 put() 的编写就容易了,当来一个新关键字的时候,就把其节点插入链表头,同时更新哈希表。每当链表中的节点个数超过LRU的容量时,我们就删除掉链表尾的的元素,同时更新哈希表。

现在我们还有一个问题需要解决,就是使用普通的单链表,是否能满足put()和get()均为O(1)的题目要求。根据以上的分析,我们对链表会有以下操作:

  1. 把一个新节点插入链表头。当我们put()一个新关键字,其不存在于缓存中,且缓存没有满时,就把这个新的关键字节点插入链表头。
  2. 把一个链表中的节点移动到链表头。当我们get()一个关键字,其存在于缓存中,我们就需要把这个节点从当前位置插入到链表头部。
  3. 删除链表尾的节点。当我们put()一个新关键字,其不存在于缓存中,我们需要插入新节点,但此时缓存满了,我们需要把最近最少使用的节点从链表中剔除掉,也就是删除链表尾的节点。

根据以上对链表的要求,可以确定只有双链表能满足要求,单链表、单循环链表、带尾指针的单循环链表都无法在O(1)的时间下做到以上三个操作,大家可以模拟一下。

确定好数据结构,代码就很好写了,只需按需实现LRU功能即可:

struct doubleLinkNode {int key, value;doubleLinkNode *pre, *next;
};class LRUCache {
private:doubleLinkNode *head, *rear;int size, capacity;unordered_map<int, doubleLinkNode*> map;void insert(doubleLinkNode* node) {// 把一个非链表上的节点插入到表头node->pre = head;node->next = head->next;head->next->pre = node;head->next = node;}void insertHead(doubleLinkNode* node) {// 把一个链表上的节点插入到表头node->pre->next = node->next;node->next->pre = node->pre;insert(node);}int deleteRear() {doubleLinkNode *node = rear->pre;node->pre->next = node->next;node->next->pre = node->pre;int key = node->key;delete node;return key;}public:LRUCache(int cap): size(0), capacity(cap) {head = new doubleLinkNode();rear = new doubleLinkNode();head->next = rear;head->pre = nullptr;rear->next = nullptr;rear->pre = head;}int get(int key) {if(map.count(key)) {insertHead(map[key]);return map[key]->value;}return -1;}void put(int key, int value) {if(map.count(key)) {doubleLinkNode *node = map[key];if(node->value != value) {node->value = value;}insertHead(node);} else {doubleLinkNode *node = new doubleLinkNode;node->key = key;node->value = value;insert(node);++size;if(size > capacity) {int nodeKey = deleteRear();map.erase(nodeKey);--size;}map[key] = node;}}
};

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

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

相关文章

STM32时钟树解析

本人之前其实也用STM32做过一些小东西&#xff0c;但因为时钟的初始化一般是直接在SystemInit时钟系统初始化函数里直接配置为72MHz&#xff0c;所以对于STM32的时钟框图并没有怎么理会&#xff0c;今天刚好有空就重新看了一下并写一篇博客记录一下吧&#xff0c;以免以后又忘了…

S3C2440时钟体系

S3C2440在默认情况下&#xff0c;整个系统全靠一个12MHz的外部晶振提供频率来工作运行的&#xff0c;也就是说CPU、内存、UART、ADC等所有需要用到时钟频率的硬件都工作在12MHz下&#xff0c;但是通过查阅芯片手册我们知道CPU时钟最高可为400MHZ&#xff0c;那么怎么设置时钟让…

关于MCU、CPU扩展SDRAM的一个小知识

像上图这种硬件电路图上的16个数据位和我们在初始化SDRAM的时候设置的16位数据位宽是指我们读写SDRAM的时候可以同时读写16个数据位&#xff0c;数据线越多肯定越快&#xff0c;但是数据线也不可能无限增加&#xff0c;我们在程序里是可以读写8位&#xff0c;16位&#xff0c;3…

S3C2440扩展SDRAM

本文主要目的是记录一下S3C2440扩展SDRAM的一些知识&#xff0c;方便以后查阅。 通过查阅手册我们知道&#xff0c;2440有8个可以用来扩展内存的BANK&#xff0c;其中第6和第7还可用来扩展SDRAM 下面我们来看一下2440扩展SDRAM需要设置哪些寄存器。 一、BWSCON寄存器 该寄存器…

汇编语言的相对跳转和绝对跳转以及反汇编代码解析

上图第一行的b1 main为相对跳转&#xff0c;即跳转到pcoffset,其中pc为当前pc值&#xff0c;offset可以理解为偏移地址&#xff0c;也就是根据当前所在地址加上偏移地址实现跳转&#xff0c;为相对跳转。 我们来看看它的反汇编代码 上图清除完bss区后使用b1指令跳转到30000668…

韦东山嵌入式第一期14课第004节_und异常模示程序示例_P笔记

本节课的第一个程序韦老师是想让大家见识一下未定义异常&#xff0c;而第二个程序是对第一个程序进行改进&#xff0c;防止在某些条件下执行不了&#xff0c;下面就来讲一下第2个程序改进了哪些地方并且有什么用。 程序在此路径中&#xff1a;源码文档图片\源码\源码_20180321…

关于NOR FLASH地址左右移的问题

问题引入&#xff1a;不知道你会不会有这样的疑问&#xff1a;为什么在发送解锁命令时&#xff0c;我们不用右移一位&#xff0c;而发送扇区地址时却要右移一位&#xff08;nor_cmd函数内部已经左移一位&#xff09;&#xff0c;这里先补充说明一下说明是cpu角度和nor角度&…

在linux下利用ls命令进行模糊查找

如上图&#xff0c;我们当前路径下有三个文件&#xff0c;分别为helloworld.c以及helloworld和1.c&#xff0c;直接输入命令ls则显示所有文件&#xff0c;我们可以利用ls 加*的方向进行模糊查找。 输入ls 目录名 形式的命令行&#xff0c;则是对该目录名下的文件全部进行显示&a…

Makefile常见符号意思

Makefile里有许许多多的符号&#xff0c;对于新手而言如果没有经常使用&#xff0c;就很容易忘记&#xff0c;所以我把常见符号的意义写下&#xff0c;方便日后忘记查询。本文章会持续更新... 1.$&#xff1a;代表目标&#xff1b;$^代表所有依赖&#xff0c;$^代表第一个依赖。…

Linux下串口通信详解

https://blog.csdn.net/u010783226/article/details/73369097

fstat、stat和lstat 区别

nt fstat(int filedes, struct stat *buf); int stat(const char *path, struct stat *buf); int lstat(const char *path, struct stat *buf); 一眼就能看出来fstat的第一个参数是和另外两个不一样的&#xff0c;fstat区别于另外两个系统调用的地方在于&#xff0c;fstat系…

Linux的帧缓冲设备

Linux的帧缓冲设备 帧缓冲&#xff08;framebuffer&#xff09;是 Linux 为显示设备提供的一个接口&#xff0c;把显存抽象后的一种设备&#xff0c;他允许上层应用程序在图形模式下直接对显示缓冲区进行读写操作。这种操作是抽象的&#xff0c;统一的。用户不必关心物理显存的…

Linux下没有包含头文件(不知是哪个)导致编译无法通过的解决心得

最近写程序的时候编译出错了&#xff0c;提示信息为&#xff1a;invalid use of undefined type fb_var_screeninfo。显示根据英文知道是没有定义 fb_var_screeninfo这个类型&#xff0c;明显是缺少了某个头文件&#xff0c;但是缺少哪个头文件以及有什么又快又好的解决方法呢&…

gcc编译缺少数学库

Linux下编译出现以下提示可以在编译的后面加上-lm&#xff0c;例如&#xff0c;arm-none-linux-gnueabi-gcc -o example1 example1.c -lm&#xff0c;意思就是添加数学库的意思&#xff0c;编译就能通过了 example1.c:(.text0x3e8): undefined reference to cos example1.c:(.…

Linux编译程序时加-I指定头文件位置

Linux下编译出现以下错误&#xff0c;错误的原因是在/usr/local/arm/arm-2009q3/bin/../arm-none-linux-gnueabi/libc/usr/include/freetype/config/下找不到ftheader.h&#xff0c;而我到该目录下看&#xff0c;发现路径是这样的rootubuntu:/usr/local/arm/arm-2009q3/arm-non…

树莓派远程监控的实现

原文&#xff1a;https://blog.csdn.net/ayz123456/article/details/79252923 http://shumeipai.nxez.com/2016/09/01/raspberry-pi-motion-cameras-for-remote-monitoring.html https://blog.csdn.net/wto882dim/article/details/82195001 https://blog.csdn.net/qq_3950082…

公网访问树莓派

公网访问树莓派控制小车 上篇已经介绍了小车在局域网中的控制方法&#xff0c;比较简单&#xff0c;既然是远程遥控那就要能够进行公网访问&#xff0c;使得你的小车可以在任何有网络的地方都能访问到&#xff0c;并且后续还会加上摄像头&#xff0c;进行实时监控&#xff0c;想…

关于对象的引用作为参数,可以直接访问私有成员的问题

#include using namespace std; class CPoint { public:CPoint(int xx, int yy){x xx;y yy;}CPoint(const CPoint &p){x p.x;y p.y;} private:int x, y; };首先&#xff0c;我们来看一个例子&#xff0c;在CPoint这个类中定义了两个构造函数&#xff0c;第一个为普通的…

僵死进程的产生以及解决办法

本文参考自&#xff1a;https://baike.baidu.com/item/%E5%83%B5%E5%B0%B8%E8%BF%9B%E7%A8%8B/1036577?fraladdin 一个进程在调用exit命令结束自己的生命的时候&#xff0c;其实它并没有真正的被销毁&#xff0c;而是留下一个称为僵尸进程&#xff08;Zombie&#xff09;的数据…