首页 > 编程语言 >算法:LRU 和 LFU 缓存淘汰算法

算法:LRU 和 LFU 缓存淘汰算法

时间:2024-05-29 14:46:14浏览次数:22  
标签:缓存 get cache LFU 访问 算法 LRU key freq

零、资料

 

一、基本概念

LRU(Least Recently Used)和LFU(Least Frequently Used)是两种常见的缓存淘汰算法, 用于在缓存空间有限的情况下选择合适的缓存对象进行淘汰,以提高缓存的利用效率
  • LRU算法基于"最近最少使用"的原则进行淘汰。它维护一个缓存的访问顺序链表,当有新的数据被访问时,如果数据已经在缓存中,则将其移到链表头部;如果数据不在缓存中,则将其添加到链表头部。当需要淘汰数据时,选择链表尾部的数据进行淘汰,因为尾部的数据是最近最少被访问的数据
  • LFU算法基于"最不经常使用"的原则进行淘汰。它维护一个缓存对象的访问频次,对于每个访问到的对象,增加其访问频次。当需要淘汰数据时,选择访问频次最低的数据进行淘汰

LRU 和 LFU 算法都有各自的优势和适用场景:
  • LRU算法适用于访问具有时间局部性的数据,即最近被访问的数据可能在将来一段时间内仍然会被访问。LRU算法相对较简单,容易实现,适用于大多数场景。但是,当存在"热点数据"(被频繁访问的数据)时,LRU算法可能无法有效地保证缓存的命中率
  • LFU算法适用于访问具有访问频次局部性的数据,即访问频次高的数据很可能在将来一段时间内仍然会被频繁访问。LFU算法需要维护每个对象的访问频次计数,相对于LRU算法来说更加复杂。LFU算法在面对热点数据的场景下表现较好,但在某些场景下可能存在"频次突变"的问题,即频次高的数据突然不再被访问,但因为频次计数较高而长时间无法被淘汰

二、代码实现及分析

LRU

class LRUCache {
  constructor(capacity) {
    this.capacity = capacity;
    // Map 的特性:记住键的原始插入顺序
    // 因此,最后插入的一定是最新鲜的,第一个也是最不新鲜的
    this.cache = new Map();
  }

  get(key) {
    if (!this.cache.has(key)) return -1;

    const value = this.cache.get(key);
    
    // 调用过了 get,表明这个数据最近被消费,所以可以挪到 Map 的尾部
    this.cache.delete(key);
    this.cache.set(key, value);

    return value;
  }

  put(key, value) {
    if (this.cache.has(key)) {
      this.cache.delete(key);
    } else if (this.cache.size >= this.capacity) { // 超出缓存容量
      // 获取 Map 的首位数据,即最旧数据
      const oldestKey = this.cache.keys().next().value;

      this.cache.delete(oldestKey);
    }

    this.cache.set(key, value);
  }
}

// 示例用法
const cache = new LRUCache(2); // 创建容量为2的LRU缓存

cache.put(1, 1);
cache.put(2, 2);
console.log(cache.get(1)); // 输出 1

cache.put(3, 3);
console.log(cache.get(2)); // 输出 -1
console.log(cache.get(3)); // 输出 3

cache.put(4, 4);
console.log(cache.get(1)); // 输出 -1
console.log(cache.get(3)); // 输出 3
console.log(cache.get(4)); // 输出 4

LFU

class LFUCache {
  constructor(capacity) {
    this.capacity = capacity;
    this.cache = new Map();

    this.frequency = new Map();
    this.minFrequency = 0;
  }

  get(key) {
    if (!this.cache.has(key)) return -1;

    const { value, freq } = this.cache.get(key);

    // 更新访问次数
    this.updateFrequency(key, freq);

    return value;
  }

  put(key, value) {
    if (this.capacity === 0) return;

    if (this.cache.has(key)) {
      const { freq } = this.cache.get(key);

      this.cache.set(key, { value, freq });
      this.updateFrequency(key, freq); 

      return;
    }

    if (this.cache.size >= this.capacity) {
      const leastFreq = this.frequency.get(this.minFrequency);
      const deleteKey = leastFreq.keys().next().value;

      leastFreq.delete(deleteKey);
      this.cache.delete(deleteKey);
    }

    this.cache.set(key, { value, freq: 1 });

    if (!this.frequency.has(1)) this.frequency.set(1, new Set());

    this.frequency.get(1).add(key);
    this.minFrequency = 1; // 新添加元素时最小访问次数更新为 1
  }

  updateFrequency(key, freq) {
    const freqList = this.frequency.get(freq);

    freqList.delete(key);

    // 当前元素旧的访问次数等于最小访问次数并且当前访问次数 freq 组成的 Set 为空
    //(元素旧的访问次数等于最小访问次数,并且最小访问次数对应的 Set 为空的情况)
    // 如果某个元素它之前的访问次数为最小访问次数,而且没有其他元素与该元素的访问次数相同
    if (freq === this.minFrequency && freqList.size === 0) this.minFrequency++;

    if (!this.frequency.has(freq + 1)) this.frequency.set(freq + 1, new Set());

    // 在更新后的访问次数对应的 Set 里面添加当前元素对应的 key
    this.frequency.get(freq + 1).add(key);
    this.cache.get(key).freq = freq + 1; // 更新当前元素的访问次数 + 1
  }
}

// 示例用法
const cache = new LFUCache(2); // 创建容量为2的LFU缓存

cache.put(1, 1);
cache.put(2, 2);
console.log(cache.get(1)); // 输出 1

cache.put(3, 3); // 删除 1
console.log(cache.get(2)); // 输出 2

console.log(cache.get(3)); // 输出 3

cache.put(4, 4); // 删除 2
console.log(cache.get(2)); // 输出 -1
console.log(cache.get(3)); // 输出 3
console.log(cache.get(4)); // 输出 4

LFU 的整体思路是在 LRU 的基础上再维护一个 Map,这个 Map 以新鲜度(从 1 开始)为 key,分别记录对应新鲜度的数据在 cache 中的 Key,从而再去 cache 中查到对应的具体数据

标签:缓存,get,cache,LFU,访问,算法,LRU,key,freq
From: https://www.cnblogs.com/cc-freiheit/p/18220256

相关文章

  • 关于希尔算法的学习笔记
    希尔算法的简介希尔算法是插入算法的升级版,D.L.Shell于1959提出,是一种减少增量算法,提出的过程为作者发现插入算法的时间复杂度会随着数组的有序性上升而下降,所以采用分组的算法,使各个组内变得有序,提升整体的有序性,从而减少插入算法的时间.希尔算法的原理比如说我......
  • 人脸识别——Webface-OCC遮挡人脸识别算法解析
    1.概述自2019年被誉为人脸识别技术的元年,各地纷纷引入这项技术。然而,自2020年起,为了抵御冠状病毒(COVID-19)的全球传播,人们普遍开始佩戴口罩。众所周知,现有人脸识别模型在面对遮挡物(如口罩)时,其识别精度会显著下降。这一现象的主要原因在于,现有数据集往往没有充分考虑遮挡因......
  • (算法)双指针——快乐数 <数据分块>
    1.题⽬链接:快乐数2.题⽬描述:3.题⽬分析: 为了⽅便叙述,将「对于⼀个正整数,每⼀次将该数替换为它每个位置上的数字的平⽅和」这⼀个操作记为x操作; 题⽬告诉我们,当我们不断重复x操作的时候,计算⼀定会「死循环」,死的⽅式有两种:         ▪情况⼀:⼀直在1中......
  • (算法)双指针——复写零 <原地复写>
    1.题⽬链接:1089.复写零2.题⽬描述:3.解法(原地复写-双指针): 算法思路: 如果「从前向后」进⾏原地复写操作的话,由于0的出现会复写两次,导致没有复写的数「被覆盖掉」。因此我们选择「从后往前」的复写策略。但是「从后向前」复写的时候,我们需要找到「最后⼀个复写的数......
  • 二分查找算法详讲(三种版本写法)原创
    介绍:二分查找算法(BinarySearch)是一种在有序数组中查找目标元素的算法。它的基本思想是通过将目标元素与数组的中间元素进行比较,从而将搜索范围缩小一半。如果目标元素等于中间元素,则搜索结束;如果目标元素小于中间元素,则继续在左半部分查找;如果目标元素大于中间元素,则在右......
  • 基于BP神经网络的32QAM解调算法matlab性能仿真
    1.算法运行效果图预览   2.算法运行软件版本matlab2022a 3.算法理论概述       32QAM(QuadratureAmplitudeModulation,四相幅度调制)是一种高效的数字调制技术,能够在一个信道内同时传输多比特信息。基于BP(Backpropagation)神经网络的32QAM解调算法,利用神经网......
  • 数据结构与算法学习——二叉树
    题目PS:下列题目均来自leetcode中灵神题单938.二叉搜索树的范围和classSolution:defrangeSumBST(self,root:TreeNode,low:int,high:int)->int:ifnotroot:return0ifroot.val>high:returnself.rangeSumBST(r......
  • 数据结构与算法
    数据结构与算法导航目录数据结构与算法导航一、数据结构与算法概念数据结构的定义算法伪代码二、线性表线性表三、队列与栈栈队循环队列四、窜广义表窜五、数组六、树与二叉树树二叉树七、图图的存储八、查找五大查找顺序查找二分查找二叉查找树(排序)二叉平衡树哈夫曼树B-树B+......
  • 旅行第三天【算法】双指针-----盛最多水的容器
    文章目录一、题目二、算法原理三、编写代码一、题目链接:盛最多水的容器二、算法原理首先,这种题可以用暴力解法(枚举每一种容器的大小情况),但是显然会超时(不用尝试啦,我已经试过啦!)其次还是咱们的主题----->利用双指针来求解下面先附上草稿图容器面积=高度(左......
  • 【LeetCode算法】第88题:合并两个有序数组
    目录一、题目描述二、初次解答三、官方解法四、总结一、题目描述二、初次解答1.思路:首次想到的解法:定义一个m+n长度的辅助数组,从头遍历这两个数组,谁小就放进辅助数组中并且对应往后走,最后使用memcpy函数将辅助数组内容拷贝到nums1中。这种解法容易想到,但是空间复杂......