LRU 居然翻译成最近最少使用?真相原来是这样!(附力扣题解)
前言
相信有很多同学和我一样,第一次碰到 LRU(Least Recently Used) 的这个解释「最近最少使用」都不知道是什么意思,用汤家凤老师的话来说:
我真的感到匪夷所思啊!
最近是表示时间,最少是表示频度,拆开来都知道,但是合在一起就不知道是什么意思了。经过一番搜索后,我发现这可能是国内一些专业名词的通病:翻译问题。甚至百度百科对 LRU 的解释也是这样:
LRU 是 Least Recently Used 的缩写,即最近最少使用,是一种常用的页面置换算法,选择最近最久未使用的页面予以淘汰。该算法赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来所经历的时间 t,当须淘汰一个页面时,选择现有页面中其 t 值最大的,即最近最少使用的页面予以淘汰。
什么叫「选择最近最久未使用的页面予以淘汰」?今天我们就来摸一摸它的底!
LRU 算法过程
LRU 常见的实现是使用一个双向链表保存缓存数据,算法步骤如下:
- 插入新数据时会插入到链表头部;
- 当缓存数据被访问时,将该数据移到链表尾部;
- 当链表满的时候,将链表头部的数据丢弃。
看完算法过程,内心惊呼:这不就是当容量不够用的时候淘汰最久没访问的数据么,和最少有个毛关系啊!按我的理解,Least Recently 其实应该翻译成 「最不是最近的」也就是最远的,Least 其实是修饰 Recently,而不应该和它并列翻译成「最近最少」,LRU 说到底就是一个时间维度上的缓存优化算法。
LRU 的兄弟 LFU
LFU(Least Frequently Used)和 LRU 比较相似,也是缓存优化算法,但是他和 LRU 唯一的区别就是,LRU 是时间维度上的,当缓存满的时候,将最久没使用的数据丢弃,而 LFU 是频率维度上的,会将最少使用(使用频次最低)的数据丢弃。
百度百科上对 LFU 的解释如下:
LFU(least frequently used (LFU) page-replacement algorithm)。即最不经常使用页置换算法,要求在页置换时置换引用计数最小的页,因为经常使用的页应该有一个较大的引用次数。但是有些页在开始时使用次数很多,但以后就不再使用,这类页将会长时间留在内存中,因此可以将引用计数寄存器定时右移一位,形成指数衰减的平均使用次数。
这就有点搞笑了, LFU 的翻译是「最不经常使用」,既然作为 LRU 的兄弟,难道你不应该翻译成「最经常最少使用」算法吗? 不过这好像也侧面印证了我上面的猜想应该是正确的。
同样的翻译灾难
鲁棒性
这个名词乍一听,这是什么玩意儿,完全不能从字面意思上推测出实际的含义,结果一查阅发现,这其实是英文 Robustness 的翻译,应该是直接音译的,更直观也更好听的翻译应该是「健壮性」、「稳健性」。
有理数
有人提过一个问题:有理数为什么叫「有理数」?难道是有道理的数?这个答案显然很离谱,但是我们大多数人也就得过且过吧,没有去较真了。但是细加挖掘,会发现有理数的英文是 rational number,其中 rational 被翻译成了「合理的」、「有道理的」,实际上 rational 还有一个意思是「可比的」,也就是能表示成两个整数之比的数是有理数。
这里面还有一段很有意思的历史,感兴趣的可以自行查阅。
总结
从这次经历来看,以后大家要是在一些专业名词上有困惑,不妨看看它翻译之前的英文,结合专业名称具体的含义多多推敲,如果是一个算法,就理解它的算法过程。最后会发现,一切都有源头,每个名词都有它的来历,不管是翻译错误还是什么,说不定还能了解到背后的一些历史渊源。
附:LeetCode 146. LRU 缓存实现
既然说到了 LRU 的实现,我们不妨将 LeetCode 上关于 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)的平均时间复杂度运行。
首先分析一下这道题需要的数据结构,因为获取缓存元素的 get 方法需要 O(1) 的时间复杂度,因此存储元素很容易想到使用哈希表。但是再仔细读题就会发现,LRU 的核心思想「容量不够删除最远使用的元素」蕴含了数据需要以时间为维度排列成有序序列,且需要频繁在序列头部和尾部操作,因此最终选定使用双向链表。
最终,数据结构选取「哈希表+双向链表」来实现。
首先,先定义双向链表的节点:
var ListNode = function (key, value) {
this.key = key;
this.value = value;
this.prev = null;
this.next = null;
}
然后,初始化 LRU 的结构:
var LRUCache = function (capacity) {
this.capacity = capacity;
this.map = new Map(); // 哈希表
// 初始化双向链表
this.head = new ListNode();
this.tail = new ListNode();
this.head.next = this.tail;
this.tail.prev = this.head;
};
由于算法中访问过的节点要移到链表末尾,因此先实现一下节点插入末尾的操作:
LRUCache.prototype.insertTail = function (node) {
this.tail.prev.next = node;
node.prev = this.tail.prev;
node.next = this.tail;
this.tail.prev = node;
}
准备工作做好之后,就开始实现最关键的 get 和 put 方法,由于题目中已经将各个情况都描述清楚了,因此按照题目要求进行逐个实现即可。
首先是 get 方法,如果缓存中没有 key,则返回 -1。如果有 key,那么将该 key 对应的节点从链表中原来的位置删除,然后插入链表末尾,代表最近访问过。最好返回该节点对应的值:
LRUCache.prototype.get = function(key) {
if (!this.map.has(key)) return -1;
let node = this.map.get(key);
// 将 node 从原来位置删除
node.prev.next = node.next;
node.next.prev = node.prev;
this.insertTail(node); // 插入链表末尾
return node.value;
};
然后是 put 方法,如果 key 已经存在,那么就把对应的节点找出来修改值,然后移到链表末尾。如果 key 不存在,要先判断一下缓存是否已满,如果满了,要先把头部节点(最远使用)删除,并在哈希表中删除记录,再插入新节点,如果没满,就直接插入新节点:
LRUCache.prototype.put = function(key, value) {
if (this.map.has(key)) {
// 如果 key 已经存在,则修改值
let node = this.map.get(key);
node.value = value;
// 移到链表末尾
node.prev.next = node.next;
node.next.prev = node.prev;
this.insertTail(node);
} else {
// 如果 key 不存在,判断是否超出容量
if (this.map.size >= this.capacity) {
let rmNode = this.head.next;
// 删除头部节点
this.head.next = rmNode.next;
rmNode.next.prev = this.head;
this.map.delete(rmNode.key);
}
// 新建节点插入链表末尾,并存入哈希表
let newNode = new ListNode(key, value);
this.map.set(key, newNode);
this.insertTail(newNode);
}
};
这道中等题在面试中出现的频率还是比较高的,因此最好还是能掌握。
LRU 居然翻译成最近最少使用?真相原来是这样!(附力扣题解)的更多相关文章
- LRU 居然翻译成最近最少使用?真相原来是这样!
前言 相信有很多同学和我一样,第一次碰到 LRU(Least Recently Used) 的这个解释「最近最少使用」都不知道是什么意思,用汤老师的话来说: 我真的感到匪夷所思啊! 最近是表示时间,最 ...
- 【探索】机器指令翻译成 JavaScript
前言 前些时候研究脚本混淆时,打算先学一些「程序流程」相关的概念.为了不因太枯燥而放弃,决定想一个有趣的案例,可以边探索边学. 于是想了一个话题:尝试将机器指令 1:1 翻译 成 JavaScript ...
- 机器指令翻译成 JavaScript —— No.2 跳转处理
上一篇,我们发现大多数 6502 指令都可以直接 1:1 翻译成 JS 代码,但除了「跳转指令」. 跳转指令,分无条件跳转.条件跳转.从另一个角度,也可分: 静态跳转:目标地址已知 动态跳转:目标地址 ...
- 机器指令翻译成 JavaScript —— No.3 流程分割
上一篇 我们讨论了跳转指令,并实现「正跳转」的翻译,但最终困在「负跳转」上.而且,由于线程模型的差异,我们不能 1:1 的翻译,必须对流程进行一些改造. 当初之所以选择翻译,而不是模拟,就是出于性能考 ...
- 机器指令翻译成 JavaScript —— No.4 动态跳转
上一篇,我们用模拟流程的方式,解决了跳转问题. 不过静态跳转,好歹事先是知道来龙去脉的.而动态跳转,只有运行时才知道要去哪.既然流程都是未知的,翻译从何谈起? 动态跳转,平时出现的多吗?非常多!除了 ...
- 机器指令翻译成 JavaScript —— No.5 指令变化
上一篇,我们通过内置解释器的方案,解决任意跳转的问题.同时,也提到另一个问题:如果指令发生变化,又该如何应对. 指令自改 如果指令加载到 RAM 中,那就和普通数据一样,也是可以随意修改的.然而,对应 ...
- 机器指令翻译成 JavaScript —— No.6 深度优化
第一篇 中我们曾提到,JavaScript 最终还得经过浏览器来解析.因此可以把一些优化工作,交给脚本引擎来完成. 现代浏览器的优化能力确实很强,但是,运行时的优化终归是有限的.如果能在事先实现,则可 ...
- 机器指令翻译成 JavaScript —— No.7 过渡语言
上一篇,我们决定使用 LLVM 来优化程序,并打算用 C 作为输入语言.现在我们来研究一下,将 6502 指令转换成 C 的可行性. 跳转支持 翻译成 C 语言,可比 JS 容易多了.因为 C 支持 ...
- 机器指令翻译成 JavaScript —— 终极目标
上一篇,我们顺利将 6502 指令翻译成 C 代码,并演示了一个案例. 现在,我们来完成最后的目标 -- 转换成 JavaScript. 中间码输出 我们之所以选择 C,就是为了使用 LLVM.现在来 ...
- EF架构~在ef中支持IQueryable级别的Contains被翻译成了Exists,性能可以接受!
回到目录 Entityframeworks很聪明 不错,非常不错!ef里的contains比linq to sql里的contains有了明显的提升,事实上,是在进行SQL语句翻译上有所提升,在lin ...
随机推荐
- 【Java SE】Day02 数据类型转换、运算符、方法入门
一.数据类型转换 1.自动转换 取值范围小在运算时会提升为取值范围大的类型 byte+int=int int+double=double 转换规则:byte.short.char-->int-- ...
- 【敏捷研发系列】前端DevOps流水线实践
作者:胡骏 一.背景现状 软件开发从传统的瀑布流方式到敏捷开发,将软件交付过程中开发和测试形成快速的迭代交付,但在软件交付客户之前或者使用过程中,还包括集成.部署.运维等环节需要进一步优化交付效率.因 ...
- python Flask 操作数据库(2)
单表操作 数据准备 from flask import Flask from flask_sqlalchemy import SQLAlchemy class Config: DEBUG = True ...
- error: expected ‘)’ before ‘PRIx64’
打印uint64时编译报错 printf("prefix:0x%"PRIx64"\n",ipv6Prefix); 解决办法:添加头文件 #include < ...
- 11、lombok日志记录
一.添加依赖: <!--lombok插件--> <dependency> <groupId>org.projectlombok</groupId> &l ...
- 总结开源项目中的常见坏实践(Bad Practice)
一些开源项目包含了各种编程的最佳实践供人参考学习和借鉴.但是也有一些开源项目虽然初衷是好的.但是包含了一些代码的坏实践.特别是对于一部分刚入行的大学生来说,可能会给到一些错误的示范.于是在此列举一些项 ...
- [编程基础] C++多线程入门8-从线程返回值
原始C++标准仅支持单线程编程.新的C++标准(称为C++11或C++0x)于2011年发布.在C++11中,引入了新的线程库.因此运行本文程序需要C++至少符合C++11标准. 8 从线程返回值 8 ...
- [OpenCV实战]50 用OpenCV制作低成本立体相机
本文主要讲述利用OpenCV制作低成本立体相机以及如何使用OpenCV创建3D视频,准确来说是模仿双目立体相机,我们通常说立体相机一般是指双目立体相机,就是带两个摄像头的那种(目就是指眼睛,双目就是两 ...
- CLISP学习(一)
Lisp Lisp发表于1960,是由数学家约翰·麦卡锡发明的. 在计算机有所发展时,数学家们对人工智能有着极大的兴趣,觉得可以实现一个共通的方法,使计算机能够处理链表中的符号数据,允许语言的处理.信 ...
- S2-008
漏洞名称 S2-008(CVE-2012-0392) 远程代码执行漏洞 利用条件 Struts 2.0.0 - Struts 2.3.17 漏洞原理 S2-008 涉及多个漏洞,Cookie 拦截器错 ...