LFU算法

148 阅读2分钟

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
    }
  }
}