【图解版】力扣第146题:LRU缓存

力扣第146题:LRU缓存

  • 一、LRU算法
    • 1. 基本概念
    • 2. LRU 和 LFU 的区别:
    • 3. 为什么 LRU 不需要记录使用频率?
  • 二、Golang代码实现
  • 三、代码图解
    • 1. LRUCache、DLinkedNode两个结构体
    • 2. 初始化结构体对象
    • 3. addToHead函数
    • 4. removeNode函数
    • 5. moveToHead函数
    • 6. removeTail函数
    • 7. Get函数
    • 8. Put函数

一、LRU算法

1. 基本概念

在 LRU 算法中,首部节点的含义是最近最常访问的节点,而不是使用频率最高的节点。LRU(Least Recently Used) 是一种基于最近使用时间而非使用频率的缓存淘汰算法,核心思想是:最近使用的数据应该优先保留,最近很久未使用的数据应该被淘汰。

2. LRU 和 LFU 的区别:

  • LRU(Least Recently Used):基于数据的使用时间,最近访问的节点会移动到链表头部,而最久未访问的节点会被淘汰。它只关注最后一次访问的时间,不记录具体的访问次数。
  • LFU(Least Frequently Used):基于数据的使用频率,频率最高的节点会优先保留,频率最低的节点会被淘汰。

3. 为什么 LRU 不需要记录使用频率?

在 LRU 算法中,只需要维护每个节点的访问顺序,而不需要记录节点的访问次数。每次访问某个节点时,将该节点移动到链表的头部,而最久未使用的节点则自然在链表尾部。所以要获取最近访问的节点,直接访问链表的头部节点即可。

二、Golang代码实现

type LRUCache struct {size intcapacity intcache map[int]*DLinkedNodehead, tail *DLinkedNode
}type DLinkedNode struct {key, val intprev, next *DLinkedNode
}func initDLinkedNode(key, val int) *DLinkedNode {return &DLinkedNode{key: key,val: val,}
}func Constructor(capacity int) LRUCache {l := LRUCache{cache: map[int]*DLinkedNode{},head: initDLinkedNode(0, 0),tail: initDLinkedNode(0, 0),capacity: capacity,}l.head.next = l.taill.tail.prev = l.headreturn l
}func (this *LRUCache) addToHead(node *DLinkedNode) {node.prev = this.headnode.next = this.head.nextthis.head.next.prev = nodethis.head.next = node
}func (this *LRUCache) removeNode(node *DLinkedNode) {// 将节点从链表中单独抽出来node.prev.next = node.nextnode.next.prev = node.prev
}func (this *LRUCache) moveToHead(node *DLinkedNode) {this.removeNode(node)this.addToHead(node)
}func (this *LRUCache) removeTail() *DLinkedNode {node := this.tail.prevthis.removeNode(node)delete(this.cache, node.key)return node
}// 通过cache的map关系,找到对应的值,该值存储在node的val属性中。
func (this *LRUCache) Get(key int) int {// 如果key是不存在的,那就返回-1if _, ok := this.cache[key]; !ok {return -1}node := this.cache[key]this.moveToHead(node)return node.val
}func (this *LRUCache) Put(key int, val int)  {if _, ok := this.cache[key]; !ok {node := initDLinkedNode(key, val)this.cache[key] = nodethis.addToHead(node)this.size++if this.size > this.capacity {removed := this.removeTail()delete(this.cache, removed.key)this.size--}} else {node := this.cache[key]node.val = valthis.moveToHead(node)}
}

三、代码图解

1. LRUCache、DLinkedNode两个结构体

type LRUCache struct {size intcapacity intcache map[int]*DLinkedNodehead, tail *DLinkedNode
}type DLinkedNode struct {key, val intprev, next *DLinkedNode
}

在这里插入图片描述

map理解为一个存储键值对映射的地方,来(1,1),就存储(1,1),来(2,2),就存储(2,2)。

至于这些(key,val)的顺序,就用链表来控制。为了方便插入、删除节点,所以采用双向链表。

将map和双向链表结合起来,就是将map的val值设置为DoubleNode类型(双向链表类型),DoubleNode里面设置有key,val两个属性(不是映射哦),这里的key和map的key是一样大小的值。

最后的效果就是:通过map的key找到DoubleNode节点,然后找到该节点里面的val属性,至于(key,val)的顺序,是由双向链表去排的,map就是个映射到节点的地方,找到节点,就等于找到val。

2. 初始化结构体对象

func initDLinkedNode(key, value int) *DLinkedNode {return &DLinkedNode{key: key,value: value,}
}func Constructor(capacity int) LRUCache {l := LRUCache{cache: map[int]*DLinkedNode{},head: initDLinkedNode(0, 0),tail: initDLinkedNode(0, 0),capacity: capacity,}l.head.next = l.taill.tail.prev = l.headreturn l
}

在这里插入图片描述

3. addToHead函数

func (this *LRUCache) addToHead(node *DLinkedNode) {node.prev = this.headnode.next = this.head.nextthis.head.next.prev = nodethis.head.next = node
}

请添加图片描述

注意:这里关于节点的顺序,其实是在结构体外去排的,这个节点的顺序并不是排在两个结构体内的哦。

4. removeNode函数

// 将节点从链表中单独抽出来
func (this *LRUCache) removeNode(node *DLinkedNode) {node.prev.next = node.nextnode.next.prev = node.prev
}

请添加图片描述

5. moveToHead函数

func (this *LRUCache) moveToHead(node *DLinkedNode) {this.removeNode(node)this.addToHead(node)
}

把node节点从链表中打断,抽出来,然后将node节点移到this.head后面

6. removeTail函数

func (this *LRUCache) removeTail() *DLinkedNode {node := this.tail.prevthis.removeNode(node)delete(this.cache, node.key)return node
}

请添加图片描述
因为map的key无法直接获得,而node.key和map的key一样,所以用node.key。

7. Get函数

// 通过cache的map关系,找到对应的值,该值存储在node的val属性中。
func (this *LRUCache) Get(key int) int {// 如果key是不存在的,那就返回-1if _, ok := this.cache[key]; !ok {return -1}node := this.cache[key]this.moveToHead(node)return node.val
}

8. Put函数

func (this *LRUCache) Put(key int, val int)  {if _, ok := this.cache[key]; !ok {node := initDLinkedNode(key, val)this.cache[key] = nodethis.addToHead(node)this.size++if this.size > this.capacity {removed := this.removeTail()delete(this.cache, removed.key)this.size--}} else {node := this.cache[key]node.val = valthis.moveToHead(node)}
}

!ok是表示如果map里面的key为空,那就创建一个相应的新节点,让map存储一下key和该节点的映射关系,然后将该节点插入到链表头部,

如果超过LRUCahce的容量,就删除最后一个节点。

如果map里面的key不是空的,那就更新一下map存储的节点,然后将其移动到头部。

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

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

相关文章

rust grpc demo

文章目录 1. 创建项目2. 配置proto2.1 配置Cargo.toml, 内容如下:2.2 创建文件proto/hello.proto, 内容如下:2.3 添加build.rs文件, 内容如下:2.4 项目结构如下:2.5 编译proto文件 3.0 处理服务3.1 项目引入3.2 添加sr…

多模态大语言模型(MLLM)-Deepseek Janus

论文链接:https://arxiv.org/abs/2410.13848 代码链接:https://github.com/deepseek-ai/Janus 本次解读Janus: Decoupling Visual Encoding for Unified Multimodal Understanding and Generation 前言 Deepseek出品,必属精品。 创新点 传…

docker容器无法连接宿主机mysql排查

1、docker无法访问宿主机 在docker容器内,需要访问当前docker容器的网关,例如172.xx.0.1,即可访问宿主机,因此需要保证docker的网络配置正确 查看当前docker容器的网关: docker inspect 你的容器名或者容器id 显示…

【纯前端excel导出】vue2纯前端导出excel,使用xlsx插件,修改样式、合并单元格

官网: 1、xlsx-js-style xlsx-js-style | xlsx-js-style homepage 2、xlsx SheetJS 中文网 一、使用第三方插件 1、安装 npm install xlsx-js-style 2、引入 import xlsx from xlsx-js-style xlsx插件是基础的导出,不可以修改样式,直接xlsx-s…

文通车牌识别相机在工地称重应用中的卓越表现

在现代工地管理中,高效、准确的称重系统是确保工程顺利进行的关键之一。而文通车牌识别相机的出现,为工地称重应用带来了全新的解决方案。 一、工地称重面临的挑战 传统的工地称重方式往往存在着一些问题。人工记录车牌和重量信息容易出现错误&#xff0…

python-----函数详解(一)

一、概念及作用: 概念:由若干条语句组成语句块,其中包括函数名称、参数列表,它是组织代码的最小单元,完成一定的功能 作用:把一个代码封装成一个函数,一般按功能组织一段代码 目的就是为了重…

基于单片机的智能小区门禁系统设计(论文+源码)

1总体架构 智能小区门禁系统以STM32单片机和WiFi技术为核心,STM32单片机作为主控单元,通过WiFi模块实现与手机APP的连接,构建整个门禁系统。系统硬件包括RFID模块、指纹识别模块、显示屏、按键以及继电器。通过RFID绑定IC卡、APP面部识别、指…

百度搜索推广和信息流推广的区别,分别适用于什么场景!

信息流推广和搜索广告,不仅仅是百度,是很多平台的两个核心推广方式。 1、搜索广告: 就是基于用户的搜索习惯,更多是用户有疑问、还有用户当下就要做出行动的广告。 比如上门服务、线上咨询服务、招商加盟、了解产品各种型号和信…

STM32G4系列MCU的低功耗模式介绍

目录 概述 1 认识低功耗模式 1.1 低功耗模式的应用 1.2 低功耗模式介绍 2 低功耗模式的状态关系 2.1 低功耗模式可能的转换状态图 2.2 低功耗模式总结 3 运行模式 3.1 减慢系统时钟 3.2 外围时钟门控 3.3 低功耗运行模式(LP运行) 概述 本文主…

react 基础学习笔记

1.react 语法 ①数据渲染 函数组件将HTML结构直接写在函数的返回值中 JSX只能有一个根元素 JSX插值写法 插值可以使用的位置 1.标签内容; 2.标签属性 JSX 条件渲染:三目运算符; JSX根据数据进行列表渲染:map()方法&#x…

QT 机器视觉 1.相机类型

本专栏从实际需求场景出发详细还原、分别介绍大型工业化场景、专业实验室场景、自动化生产线场景、各种视觉检测物体场景介绍本专栏应用场景 更适合涉及到视觉相关工作者、包括但不限于一线操作人员、现场实施人员、项目相关维护人员,希望了解2D、3D相机视觉相关操作…

微服务与多租户详解:架构设计与实现

引言 在现代软件开发领域,微服务架构和多租户架构是两个重要的概念。微服务架构通过将应用程序拆分为多个独立的服务,提升了系统的灵活性和可维护性。而多租户架构则通过共享资源来服务多个客户,提高了资源利用率和系统的经济性。 一、微服务…

OpenCV的常用与形状形状描述相关函数及用法示例

OpenCV提供了提供了多种用于形状描述和分析的函数。这些函数能够帮助你提取图像中的形状特征,进行形状匹配、识别和分析。下面介绍一些常用的形状描述函数: 轮廓检测函数findContours() findContours()函数用于在二值图像中查找轮廓。有两个原型函数&…

【zlm】 webrtc源码讲解(二)

目录 webrtc播放 MultiMediaSourceMuxer里的_ring webrtc播放 > MediaServer.exe!mediakit::WebRtcPlayer::onStartWebRTC() 行 60 CMediaServer.exe!mediakit::WebRtcTransport::OnDtlsTransportConnected(const RTC::DtlsTransport * dtlsTransport, RTC::SrtpSession::…

tomcat部署war包部署运行,IDEA一键运行启动tomacat服务,maven打包为war包并部署到tomecat

tomcat部署war包前端访问 在Java Web开发中,Tomcat是一个非常流行的开源Web服务器和Servlet容器。它实现了Java Servlet和JavaServer Pages (JSP) 技术,提供了一个纯Java的Web应用环境。本文将介绍如何在Tomcat中部署运行WAR包,让你的应用快…

vue2 使用环境变量

一. 在根目录下创建.env.xxx文件 .env 基础系统变量,无论何种环境,都可使用其中配置的值,其他环境中的变量会覆盖.env中的同名变量。 .env.development 开发环境 .env.production 生产环境 .env.staging 测试环境 二. 内容格式 vue2 使用是以…

GRU神经网络理解

全文参考以下B站视频及《神经网络与深度学习》邱锡鹏,侧重对GPU模型的理解,初学者入门自用记录,有问题请指正【重温经典】GRU循环神经网络 —— LSTM的轻量级版本,大白话讲解_哔哩哔哩_bilibili 更新门、重置门、学习与输出 注&a…

STM32(二十一):看门狗

WDG(Watchdog)看门狗,手动重装寄存器的操作就是喂狗。 看门狗可以监控程序的运行状态,当程序因为设计漏洞、硬件故障、电磁干扰等原因,出现卡死或跑飞现象时,看门狗能及时复位程序,避免程序陷入…

数学建模微分方程模型——传染病模型

病毒也疯狂:细说传染病微分方程模型的那些事儿 “数学是打开科学大门的钥匙,而微分方程则是理解世界变化的密码。” 大家好!今天我们要聊一聊一个既严肃又有趣的话题——传染病微分方程模型。别急,听起来高大上,其实一…

亚信安全DeepSecurity中标知名寿险机构云主机安全项目

近日,亚信安全DeepSecurity成功中标国内知名寿险机构的云主机安全项目。亚信安全凭借在云主机安全防护领域的突出技术优势,结合安全运营的能力,以“实战化”为指导,为用户提供无惧威胁攻击、无忧安全运营的一站式云安全体系&#…