Published on

LRU

基础

LRU(Least recently used,最近最少使用)。

我个人感觉这个命名少了个动词,让人理解起来怪怪的,缓存淘汰算法嘛,淘汰最近最少使用

它的核心时:如果数据最近被访问过,那么将来被访问的几率也更高。

简单实现

LRU 一般使用双向链表+哈希表实现,在 JavaScript 中我使用 Map 数据结构来实现缓存,因为 Map 可以保证加入缓存的先后顺序

不同的是,这里是把 Map cache 的尾当头,头当尾。

class LRU {
    constructor(size) {
        this.cache = new Map()
        this.size = size
    }
    // 新增时,先检测是否已经存在
    put(key, value) {
        if (this.cache.has(key)) this.cache.delete(key)
        this.cache.set(key, value)
        // 检查是否超出容量
        if (this.cache.size > this.size) {
            this.cache.delete(this.cache.keys().next().value) // 删除Map cache 的第一个数据
        }
    }
    // 访问时,附件重新进入缓存池的动作
    get(key) {
        if (!this.cache.has(key)) return -1
        const temp = this.cache.get(key)
        this.cache.delete(key)
        this.cache.set(key, temp)
        return temp
    }
}

分析:

  1. cache 中的元素必须有时序, 便于后面删除需要淘汰的那个
  2. 在 cache 中快速找到某个 key,判断是否存在并且得到对应的 val O(1)
  3. 访问到的 key 需要被提到前面, 也就是说得能实现快速插入和删除 O(1)

lc.146 LRU 缓存

/**
 * @param {number} capacity
 */
var LRUCache = function (capacity) {
    this.cache = new Map()
    this.capacity = capacity
}

/**
 * @param {number} key
 * @return {number}
 */
LRUCache.prototype.get = function (key) {
    if (!this.cache.has(key)) return -1
    const val = this.cache.get(key)
    this.cache.delete(key)
    this.cache.set(key, val)
    return val
}

/**
 * @param {number} key
 * @param {number} value
 * @return {void}
 */
LRUCache.prototype.put = function (key, value) {
    if (this.cache.has(key)) this.cache.delete(key)
    this.cache.set(key, value)
    if (this.cache.size > this.capacity) {
        this.cache.delete(this.cache.keys().next().value)
    }
}

/**
 * Your LRUCache object will be instantiated and called as such:
 * var obj = new LRUCache(capacity)
 * var param_1 = obj.get(key)
 * obj.put(key,value)
 */

双向链表版本

class ListNode {
    constructor(key = 0, value = 0) {
        this.key = key
        this.value = value
        this.prev = null
        this.next = null
    }
}

/**
 * @param {number} capacity
 */
var LRUCache = function (capacity) {
    this.capacity = capacity
    this.cache = new Map()
    this.head = new ListNode()
    this.tail = new ListNode()
    this.head.next = this.tail
    this.tail.prev = this.head
}

/**
 * @param {number} key
 * @return {number}
 */
LRUCache.prototype.get = function (key) {
    const node = this.cache.get(key)

    if (node) {
        node.prev.next = node.next
        node.next.prev = node.prev

        node.next = this.head.next
        node.prev = this.head.next.prev
        this.head.next.prev = node
        this.head.next = node
    }

    return node ? node.value : -1
}

/**
 * @param {number} key
 * @param {number} value
 * @return {void}
 */
LRUCache.prototype.put = function (key, value) {
    let node = null
    if (this.cache.has(key)) {
        node = this.cache.get(key)
        node.value = value
        node.prev.next = node.next
        node.next.prev = node.prev
    } else {
        node = new ListNode(key, value)
    }

    node.next = this.head.next
    node.prev = this.head.next.prev
    this.head.next.prev = node
    this.head.next = node

    if (!this.cache.has(key) && this.cache.size === this.capacity) {
        // remove
        this.cache.delete(this.tail.prev.key)
        this.tail.prev = this.tail.prev.prev
        this.tail.prev.next = this.tail
    }

    this.cache.set(key, node)
}

/**
 * Your LRUCache object will be instantiated and called as such:
 * var obj = new LRUCache(capacity)
 * var param_1 = obj.get(key)
 * obj.put(key,value)
 */

小心指针的变换就好了。

LFU

LFU(Least Frequently Used,最少频繁使用)。 也就是使用最不频繁的将被淘汰,这个我目前就是先了解一下,后续需要深入的时候再回过头来看看。😂

核心思想:删除使用频次最低的键值对。如果最低的键值对有多个,则删除其中最旧的那个。

  1. 需要一个 key, value 映射
  2. 需要一个 key, freq 映射
  3. 这个需求应该是 LFU 算法的核心,所以我们分开说。
    • 首先,肯定是需要 freq 到 key 的映射,用来找到 freq 最小的 key。
    • 将 freq 最小的 key 删除,那你就得快速得到当前所有 key 最小的 freq 是多少。想要时间复杂度 O(1) 的话,肯定不能遍历一遍去找,那就用一个变量 minFreq 来记录当前最小的 freq 吧。
    • 可能有多个 key 拥有相同的 freq,所以 freq 对 key 是一对多的关系,即一个 freq 对应一个 key 的列表。
    • 希望 freq 对应的 key 的列表是存在时序的,便于快速查找并删除最旧的 key。
    • 希望能够快速删除 key 列表中的任何一个 key,因为如果频次为 freq 的某个 key 被访问,那么它的频次就会变成 freq+1,就应该从 freq 对应的 key 列表中删除,加到 freq+1 对应的 key 的列表中。

小提示:LinkedHashSet 这篇文章不错:https://halfrost.com/lru_lfu_interview/