LFU: Least Frequency Used, 最少最不经常使用算法,是一种缓存淘汰算法,如手机后台软件的运行。
LFU算法的使用
// cnt(x) = 键 x 的使用计数
// cache=[] 将显示最后一次使用的顺序(最左边的元素是最近的)
LFUCache lfu = new LFUCache(2);
lfu.put(1, 1); // cache=[1,_], cnt(1)=1
lfu.put(2, 2); // cache=[2,1], cnt(2)=1, cnt(1)=1
lfu.get(1); // 返回 1
// cache=[1,2], cnt(2)=1, cnt(1)=2
lfu.put(3, 3); // 去除键 2 ,因为 cnt(2)=1 ,使用计数最小
// cache=[3,1], cnt(3)=1, cnt(1)=2
lfu.get(2); // 返回 -1(未找到)
lfu.get(3); // 返回 3
// cache=[3,1], cnt(3)=2, cnt(1)=2
lfu.put(4, 4); // 去除键 1 ,1 和 3 的 cnt 相同,但 1 最久未使用
// cache=[4,3], cnt(4)=1, cnt(3)=2
lfu.get(1); // 返回 -1(未找到)
lfu.get(3); // 返回 3
// cache=[3,4], cnt(4)=1, cnt(3)=3
lfu.get(4); // 返回 4
// cache=[3,4], cnt(4)=2, cnt(3)=3
从上面的操作过程,可以看出LFU需要具备以下条件:
- 快速找到某个key是否存在,并得到对应的value
- 在访问某个key后,该key的freq需要加一
- 如果容量满了的时候进行插入,需要将freq最小的key删除,如果最小freq对应多个key,删除其中最旧的那个
- 当前最小freq可以通过一个变量进行保存和更新
双向链表实现
class DoubleLinkNode {
constructor(key, val) {
this.key = key
this.val = val
this.freq = 1
this.prev = null
this.next = null
}
incFreq() {
this.freq++
}
}
class DoubleLink {
constructor() {
this.head = new DoubleLinkNode(-1, -1)
this.tail = new DoubleLinkNode(-1, -1)
this.head.next = this.tail
this.tail.prev = this.head
this.size = 0
}
// 链表头部插入新节点
unshift(node) {
node.prev = this.head
node.next = this.head.next
this.head.next.prev = node
this.head.next = node
this.size++
}
// 删除某个节点
delete(node) {
node.next.prev = node.prev
node.prev.next = node.next
delete node.prev
delete node.next
this.size--
}
// 链表头部删除节点并返回
pop() {
if (this.size === 0) return undefined
const node = this.tail.prev
this.delete(node)
return node
}
}
LFU实现
class LFUCache {
constructor(cap) {
this.cap = cap
this.map = new Map() // key -> Node
this.freqArr = [new DoubleLink(), new DoubleLink()]
this.minFreq = 1
}
get(key) {
const node = this.map.get(key)
if (!node) return -1
this.updateFreq(node)
return node.val
}
put(key, val) {
if (this.cap <= 0) return
let node = this.map.get(key)
// 节点已存在
if (node) {
this.updateFreq(node)
node.val = val
return
}
// 删除频率最小且最早访问的节点
if (this.cap <= this.map.size) {
const doubleLink = this.freqArr[this.minFreq]
const deleteNode = doubleLink.pop()
this.map.delete(deleteNode.key)
}
node = new DoubleLinkNode(key, val)
this.map.set(key, node)
this.minFreq = 1
this.freqArr[this.minFreq].unshift(node)
}
updateFreq(node){
const freq = node.freq
const doubleLink = this.freqArr[freq]
doubleLink.delete(node)
node.incFreq()
if(!this.freqArr[freq + 1]) {
this.freqArr[freq + 1] = new DoubleLink()
}
this.freqArr[freq + 1].unshift(node)
// 更新最小频率
if (this.minFreq === freq && this.freqArr[freq].size === 0) {
this.minFreq = freq + 1
}
}
}