leetcode146.LRU缓存,从算法题引入,全面学习LRU和链表哈希表知识

leetcode146. 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) 的平均时间复杂度运行。
示例:
输入
[“LRUCache”, “put”, “put”, “get”, “put”, “get”, “put”, “get”, “get”, “get”]
[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]
输出
[null, null, null, 1, null, -1, null, -1, 3, 4]

解释

LRUCache lRUCache = new LRUCache(2);
lRUCache.put(1, 1); // 缓存是 {1=1}
lRUCache.put(2, 2); // 缓存是 {1=1, 2=2}
lRUCache.get(1);    // 返回 1
lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}
lRUCache.get(2);    // 返回 -1 (未找到)
lRUCache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3}
lRUCache.get(1);    // 返回 -1 (未找到)
lRUCache.get(3);    // 返回 3
lRUCache.get(4);    // 返回 4

LRU是什么?

最近最少使用算法(LRU)是大部分操作系统为最大化页面命中率而广泛采用的一种页面置换算法。具体解释见百度百科
该算法的思路是,发生缺页中断时,选择未使用时间最长的页面置换出去。
利用 LRU 算法对例子进行页面置换的结果如图所示。当进程第一次对页面 2 进行访问时,由于页面 7 是最近最久未被访问的,故将它置换出去。之后对页面0进行了访问,导致页面1比页面0成为了最久未使用的页。所以之后当进程第一次对页面 3进行访问时,第 1 页成为最近最久未使用的页,将它换出。
在这里插入图片描述

LRU实现

在实现上,LRU通常使用一个哈希表和一个双向链表来实现。哈希表存储键和链表节点的指针,而双向链表则按照数据被访问的顺序来组织数据。当缓存满了之后,链表尾部的节点(即最近最少使用的节点)会被移除,以腾出空间给新加入的数据。

1.定义结构体

首先,定义了一个名为Dlinkednode的结构体,它代表双向链表中的一个节点。每个节点包含以下成员:

int key//键值。
int value//与键值关联的数据。
Dlinkednode* pre//指向前一个节点的指针。
Dlinkednode* nex//指向后一个节点的指针。
构造函数:Dlinkednode()是默认构造函数,初始化所有成员为默认值。
Dlinkednode(int _key, int _value)是带参数的构造函数,用于创建具有特定键值和数据的节点。

2.定义LRU缓存类

接下来,定义了一个名为LRUCache的类,它用于实现LRU缓存机制。类中包含以下成员:

Dlinkednode* head//指向双向链表头部的指针。
Dlinkednode* tail//指向双向链表尾部的指针。
unordered_map<int, Dlinkednode*> cache//哈希表,用于存储键值和对应节点的指针。
int size//当前缓存中元素的数量。
int capacity//缓存的最大容量。
LRUCache(int _capacity)是类的构造函数,它初始化缓存的最大容量,
并创建一个空的双向链表,链表的头尾节点不存储实际数据,仅用于维护链表结构。
int get(int key)//获取键值对应的数据。如果键值不存在,则返回-1。
//如果键值存在,则将其对应的节点移动到链表头部(最近使用),并返回节点的数据。
void put(int _key, int _value)//添加或更新键值对。如果键值不存在,则创建新节点并添加到链表头部。
//如果键值已存在,则更新其数据并移动节点到链表头部。
//如果添加新节点后缓存超出容量,则移除链表尾部的节点(最少使用),并从哈希表中删除对应的键值。
void movetohead(Dlinkednode* node)//将节点移动到链表头部,表示该节点最近被使用。
void removenode(Dlinkednode* node)//从链表中移除指定节点。
void addtohead(Dlinkednode* node)//将节点添加到链表头部。
Dlinkednode* removetail()//移除并返回链表尾部的节点,表示该节点是最少使用的。

3.函数具体实现

get函数首先检查哈希表中是否存在键值,如果不存在,则返回-1。如果存在,则获取对应的节点指针,并调用movetohead函数将其移动到链表头部,最后返回节点的值。

put函数首先检查哈希表中是否存在键值。如果不存在,创建新节点并将其添加到链表头部,然后更新哈希表。如果键值已存在,则更新节点的值并移动到链表头部。如果缓存已满,则调用removetail函数移除最少使用的节点,并更新哈希表。

movetohead、removenode、addtohead和removetail函数是辅助函数,用于维护双向链表的结构,确保节点可以正确地添加到头部或从尾部移除。

具体代码如下:

// 定义双向链表节点结构体
struct Dlinkednode {
    int key;           // 节点的键值
    int value;         // 节点的值
    Dlinkednode* pre;  // 指向前一个节点的指针
    Dlinkednode* nex;  // 指向后一个节点的指针

    // 构造函数,初始化节点的所有成员
    Dlinkednode() : key(0), value(0), pre(NULL), nex(NULL) {}
    Dlinkednode(int _key, int _value) : key(_key), value(_value), pre(NULL), nex(NULL) {}
};

// 定义LRU缓存类
class LRUCache {
private:
    Dlinkednode* head;  // 双向链表的头部节点,不存储实际数据
    Dlinkednode* tail;  // 双向链表的尾部节点,不存储实际数据
    unordered_map<int, Dlinkednode*> cache;  // 哈希表,存储键和节点指针的映射
    int size;  // 当前缓存中的元素数量
    int capacity;  // 缓存的最大容量

public:
    // 构造函数,初始化缓存的最大容量和双向链表的头尾节点
    LRUCache(int _capacity) : capacity(_capacity), size(0) {
        head = new Dlinkednode();
        tail = new Dlinkednode();
        head->nex = tail;  // 头部节点的下一个指向尾部节点
        tail->pre = head;  // 尾部节点的上一个指向头部节点
    }
    
    // 获取缓存中键值对应的数据
    int get(int key) {
        if (cache.count(key) == 0) {
            return -1;  // 如果键值不在缓存中,返回-1
        }
        Dlinkednode* node = cache[key];  // 获取节点指针
        movetohead(node);  // 将节点移动到链表头部,表示最近使用
        return node->value;  // 返回节点的值
    }
    
    // 添加或更新缓存中的键值对
    void put(int _key, int _value) {
        if (cache.count(_key) == 0) {
            Dlinkednode* node = new Dlinkednode(_key, _value);  // 创建新节点
            cache[_key] = node;  // 更新哈希表
            addtohead(node);  // 将新节点添加到链表头部
            ++size;  // 缓存大小加1
            if (size > capacity) {
                Dlinkednode* removed = removetail();  // 移除链表尾部节点
                cache.erase(removed->key);  // 从哈希表中删除键值
                delete removed;  // 删除节点
                --size;  // 缓存大小减1
            }
        } else {
            Dlinkednode* node = cache[_key];  // 获取已存在的节点指针
            node->value = _value;  // 更新节点的值
            movetohead(node);  // 将节点移动到链表头部
        }
    }
    
    // 将节点移动到链表头部
    void movetohead(Dlinkednode* node) {
        removenode(node);  // 从当前位置移除节点
        addtohead(node);  // 将节点添加到链表头部
    }
    
    // 从链表中移除指定节点
    void removenode(Dlinkednode* node) {
        node->pre->nex = node->nex;  // 前一个节点的下一个指向当前节点的下一个
        node->nex->pre = node->pre;  // 后一个节点的上一个指向当前节点的上一个
    }
    
    // 将节点添加到链表头部
    void addtohead(Dlinkednode* node) {
        node->nex = head->nex;  // 新节点的下一个指向头节点的下一个
        node->pre = head;  // 新节点的上一个指向头节点
        head->nex->pre = node;  // 头节点的下一个的上一个指向新节点
        head->nex = node;  // 头节点的下一个指向新节点
    }
    
    // 移除并返回链表尾部的节点
    Dlinkednode* removetail() {
        Dlinkednode* node = tail->pre;  // 获取尾部节点的前一个节点
        removenode(node);  // 从链表中移除该节点
        return node;  // 返回被移除的节点
    }
};

/**
 * 下面是LRUCache类的使用示例:
 * 创建一个容量为capacity的LRUCache对象
 * LRUCache* obj = new LRUCache(capacity);
 * 通过get(key)获取键为key的数据,如果不存在则返回-1
 * int param_1 = obj->get(key);
 * 通过put(key, value)添加或更新键为key的数据,值为value
 * obj->put(key, value);
 */

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

三种字符串的管理方式

NSString的三种实现方式 OC这个语言在不停的升级自己的内存管理&#xff0c;尽量的让自己的 OC的字符串 问题引入 在学习字符串的过程中间会遇到一个因为OC语言更新造成的问题 例如&#xff1a; int main(int argc, const char * argv[]) {autoreleasepool {NSString* str1 …

ZCU102启动镜像(详细版)

ZCU102启动镜像--详细版本 详细步骤1、安装好Vitis&#xff08;GUI界面&#xff09;、 Vivado、 Petalinux软件然后vivado这边的操作就先结束了 创建Petalinux工程编译镜像打包 详细步骤 B站参考视频链接: link 1、安装好Vitis&#xff08;GUI界面&#xff09;、 Vivado、 Pe…

SpringBoot:手动创建应用

Spring提供了在线的Spring Initialzr在线创建Spring Boot项目&#xff0c;为了更好的理解Spring Boot项目&#xff0c;这里我们选择手动创建。 1.新建Web应用 1.1 生成工程 首先要做是创建一个Java项目&#xff0c;这里我们选择使用Maven来支持&#xff0c;使用archetype:ge…

C++进阶之AVL树+模拟实现

目录 目录 一、AVL树的基本概念 1.1 基本概念 二、AVL树的模拟实现 2.1 AVL树节点的定义 2.2 插入操作 2.3 旋转操作 2.4 具体实现 一、AVL树的基本概念 1.1 基本概念 二叉搜索树虽可以缩短查找的效率&#xff0c;但如果数据有序或接近有序二叉搜索树将退化为单支树&…

【论文速读】Self-Rag框架,《Self-Rag: Self-reflective Retrieval augmented Generation》

关于前面的文章阅读《When to Retrieve: Teaching LLMs to Utilize Information Retrieval Effectively》&#xff0c;有网友问与Self-Rag有什么区别。 所以&#xff0c;大概看了一下Self-Rag这篇论文。 两篇文章的方法确实非常像&#xff0c;Self-Rag相对更加复杂一些。 When …

图数据库Neo4j——Neo4j简介、数据结构 Docker版本的部署安装 Cypher语句的入门

前言 MySQL是一种开源的关系型数据库管理系统&#xff0c;使用SQL作为其查询语言&#xff0c;常见的关系型数据库有MySQL、Oracle、SQL Server、PostgreSQL等。相关博客文章如下&#xff1a; 【合集】MySQL的入门进阶强化——从 普通人 到 超级赛亚人 的 华丽转身PostgreSQL数…

计算机系统结构之流水

一、标量流水线的主要性能 吞吐率是流水线单位时间里能流出的任务数或结果数(最大吞吐率&#xff1a;单位时间内计算机所能处理的最多指令条数)。 流水线中经过时间最长的子过程成为瓶颈子过程。最大吞吐率取决于瓶颈段的时间。 实际吞吐率&#xff1a; 加速比&#xff1a; …

教你搞一个比较简单的计时和进度条装饰器 (多线程进阶版)

简单的计时和进度条装饰器 - 多线程进阶版 这个进阶版有什么&#xff1f;话不多说上代码效果图 上一篇关于装饰器的Blog 这个进阶版有什么&#xff1f; 在上一个装饰器工作时&#xff0c;跑了20秒后就停止了。如果运行的函数跑了60秒&#xff0c;后面的40秒我们是只能等到结束…

CAD二次开发(7)- 实现Ribbon选项卡,面板,功能按钮的添加

1. 创建工程 2. 需要引入的依赖 如图&#xff0c;去掉依赖复制到本地 3. 代码实现 RibbonTool.cs 实现添加Ribbon选项卡&#xff0c;添加面板&#xff0c;以及给面板添加下拉组合按钮。 using Autodesk.Windows; using System; using System.Collections.Generic; using S…

悬剑武器库5.04版

工具介绍 悬剑5 基于“悬剑网盘”精选工具集悬剑5“飞廉”云武器库制作。 操作系统&#xff1a;Windows 10 专业版 锁屏密码&#xff1a;secquan.org 解压密码: 圈子社区secquan.org 镜像大小&#xff1a;33.1GB 系统占用空间63.0 GB 镜像导入 下载镜像&#xff0c;文末…

WordPress博客主题触屏版社区源码

下载地址&#xff1a;WordPress博客主题触屏版社区源码

【Unity之FGUI】黑神章Fairy GUI控件详解

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;就业…

python采集汽车价格数据

python采集汽车价格数据 一、项目简介二、完整代码一、项目简介 本次数据采集的目标是车主之家汽车价格数据,采集的流程包括寻找数据接口、发送请求获取响应、解析数据和持久化存储,先来看一下数据情况,完整代码附后: 二、完整代码 #输入请求页面url #返回html文档 imp…

6.3 Go 结构体(Struct)

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…

普华永道信任危机:上市公司解约风波与反思

在全球会计业界的星空中&#xff0c;普华永道无疑是那颗最为耀眼的星之一。然而&#xff0c;近日这颗星却遭遇了前所未有的信任危机。这家大名鼎鼎的四大会计师事务所之一&#xff0c;近期陷入了上市公司解约的风波之中&#xff0c;其声誉与地位正面临严峻挑战。 就在昨晚&…

Word2Vec模型的引入介绍与相关概念

一 、Word2Vec模型的背景引入 1.1 One-hot模型 One-hot模型是是用N位的状态寄存器对N个状态进行编码 如下所示&#xff0c;是有4个样本&#xff0c;每个样本都有三个特征&#xff0c;特征1表示当前样本的性别。 我们喂给算法怎么样的数据&#xff0c;算法就会给我们一个怎么…

学习笔记——IP地址网络协议——网络层(IP)协议

一、网络层(IP)协议 网络层(被称为IP层)但网络层协议并不只是IP协议&#xff0c;还包括ICMP(Internet Control Message Protocol)协议、IPX(Internet Packet Exchange)协议等。 1、IP协议 IP(Internet Protocol)本身是一个协议文件的名称&#xff0c;该协议文件的内容非常少&…

使用python统计word文档页数

使用python统计word文档页数 介绍效果代码 介绍 使用python统计word文档的页数 效果 代码 import os import comtypes.clientdef get_word_page_count(docx_path):try:# Initialize the COM objectword comtypes.client.CreateObject(Word.Application)word.Visible False…

【Qt】探索Qt绘图世界:自定义控件与视觉效果的全面指南

文章目录 前言&#xff1a;1. 绘图基本概念2. 绘制各种形状3. 绘制文字&#xff08;显示文字&#xff09;、设置画笔4. 画刷5. 绘制图片6. 特殊的绘图设备总结&#xff1a; 前言&#xff1a; 在软件开发中&#xff0c;图形用户界面&#xff08;GUI&#xff09;的设计是至关重要…

【面试题】CAP理论、BASE理论及其注册中心选型

1.CAP理论 CAP&#xff1a;指的是在一个分布式系统中&#xff0c;Consistency&#xff08;一致性&#xff09;、Availability&#xff08;可用性&#xff09;、Partition Tolerance&#xff08;分区容错性&#xff09;&#xff0c;三者不可同时获得 一致性&#xff08;C&#x…