切换主题
LRU
LRU
是Least Recently Used的缩写,即最近最少使用,是一种常用的页面置换算法,选择最近最久未使用的页面予以淘汰。
双向链表 + 哈希表
实现过程描述:
- 首先初始化双向链表与哈希表;
- 对于
get
操作,先查询哈希表是否存在该key
:- 若不存在,直接返回
-1
; - 若存在,由于
get
操作访问了该key
,需将该key
对应的节点更新至链表头部,再返回该节点值。
- 若不存在,直接返回
- 对于
put
操作,先判断key
是否在hashTable
中:- 若不存在,在链表头部插入该
key
的节点同时更新hashTable
,并且判断hashTable
的size
是否超出cap
限制: 若超出,则删除最近最不常用节点,即尾节点并更新哈希表 - 若存在,直接更新
hashTable
对应的值和链表节点(移至链表头部) 。
- 若不存在,在链表头部插入该
设计dummyHead
和dummyTail
的意义:
虚拟头尾节点,只是为了让对真实头尾节点的操作,和对其他节点的操作一致,方便快速访问头尾节点。
JavaScript
class ListNode {
constructor(key, val) {
this.key = key
this.val = val
this.prev = null // 前驱指针
this.next = null // 后继指针
}
}
class LRUCache {
constructor(capacity) {
this.cap = capacity
this.hashTable = {}
this.len = 0
// 初始化双向链表
this.dummyHead = new ListNode()
this.dummyTail = new ListNode()
this.dummyHead.next = this.dummyTail
this.dummyTail.prev = this.dummyHead
}
// 链头添加节点
addToHead(node) {
node.prev = this.dummyHead
node.next = this.dummyHead.next
this.dummyHead.next.prev = node // 先执行
this.dummyHead.next = node // 再执行
}
// 删除节点
removeNode(node) {
let prevNode = node.prev
let nextNode = node.next
prevNode.next = nextNode
nextNode.prev = prevNode
}
// 移动链表
moveToHead(node) {
this.removeNode(node)
this.addToHead(node)
}
get(key) {
const node = this.hashTable[key]
if (!node) return -1 // 不存在直接返回-1
this.moveToHead(node) // 更新链表
return node.val
}
put(key, val) {
const node = this.hashTable[key]
if (!node) { // 先判断其是否在hashTable中
const newNode = new ListNode(key, val)
this.addToHead(newNode) // key不在时,链表头部插入节点并更新hashTable
this.hashTable[key] = newNode // 存入节点
this.len++
// 判断hashTable的size是否超出cap限制
if (this.len > this.cap) {
// 删除最近最不常用节点,即尾节点并更新哈希表
const tail = this.dummyTail.prev
this.removeNode(tail)
delete this.hashTable[tail.key]
this.len--
}
} else { // key已存在直接更新hashTable和链表
node.val = val // 更新值
this.moveToHead(node)
}
}
}
hashMap
关于JavaScript中的hashMap
你可以前往MDN了解更多, 这里就说一个重要特性:
Map
保存键值对时,会记住键的原始插入顺序。
JavaScript
class LRUCache {
constructor(capacity) {
this.cap = capacity
this.map = new Map()
}
get(key) {
if (!this.map.has(key)) return -1
// 存在
const buf = this.map.get(key)
this.map.delete(key)
this.map.set(key, buf)
return buf
}
put(key, val) {
if (this.map.has(key)) this.map.delete(key)
this.map.set(key, val)
if (this.map.size > this.cap) {
// 按顺序插入,故第一个键就是被淘汰的值
this.map.delete(this.map.keys().next().value)
}
}
}