JavaScript双向链表实现LFU缓存算法

目录
  • 什么是LFU
  • 描述
  • 解题思路
    • 1、构造节点结构体
    • 2、构造双向链表
    • 3、编写链表头添加节点方法
    • 4、编写删除节点方法
    • 5、构造LRU缓存结构体
    • 6、编写get方法
    • 7、编写put方法

什么是LFU

LFU(Least Frequently Used),最近最少使用策略,也就是说在一段时间内,数据被使用频次最少的,优先被淘汰。
它是一种用于管理计算机内存的缓存算法,采用LFU算法的最简单方法是为每个加载到缓存的块分配一个计数器。每次引用该块时,计数器将增加一。当缓存达到容量并有一个新的内存块等待插入时,系统将搜索计数器最低的块并将其从缓存中删除。

描述

请你为 最不经常使用(LFU)缓存算法设计并实现数据结构。

实现 LFUCache 类:

LFUCache(int capacity) - 用数据结构的容量 capacity 初始化对象

int get(int key) - 如果键 key 存在于缓存中,则获取键的值,否则返回 -1 。

void put(int key, int value) - 如果键 key 已存在,则变更其值;如果键不存在,请插入键值对。

当缓存达到其容量 capacity 时,则应该在插入新项之前,移除最不经常使用的项。

在此问题中,当存在平局(即两个或更多个键具有相同使用频率)时,应该去除 最近最久未使用 的键。

为了确定最不常使用的键,可以为缓存中的每个键维护一个 使用计数器 。使用计数最小的键是最久未使用的键。

当一个键首次插入到缓存中时,它的使用计数器被设置为 1 (由于 put 操作)。对缓存中的键执行 get 或 put 操作,使用计数器的值将会递增。

函数 get 和 put 必须以 O(1) 的平均时间复杂度运行。

解题思路

1、构造节点结构体

保存对应的数据信息

const LFUNode = function (
  key = "",
  val = "",
  freq = 0,
  pre = null,
  next = null
) {
  this.key = key;
  this.val = val;
  this.freq = freq;
  this.pre = pre;
  this.next = next;
};

2、构造双向链表

构造带有头尾节点的双向链表,head和tail为哨兵节点,不保存信息,仅用于标记头尾。

const LFULinkLine = function (node) {
  let head = new LFUNode("head");
  let tail = new LFUNode("tail");
  head.next = node;
  tail.pre = node;
  node.pre = head;
  node.next = tail;
  this.head = head;
  this.tail = tail;
};

3、编写链表头添加节点方法

将节点插入到链表的头结点之后,其他节点往后移。

LFULinkLine.prototype.addNode = function (node) {
  this.head.next.pre = node;
  node.pre = this.head;
  node.next = this.head.next;
  this.head.next = node;
};

4、编写删除节点方法

将节点从链表中删除。

LFULinkLine.prototype.removeNode = function (node) {
  node.pre.next = node.next;
  node.next.pre = node.pre;
};

5、构造LRU缓存结构体

构造LRU缓存结构体,具体信息如下代码,capacity用于保存最大缓存数,即该类的容量;num保存当前存储的数据量,用于判别是否超出最大容量;minFreq保存当前存储的数据中的最小频率,删除的时候需要优先删除频率最小的;kvMap保存节点详细信息,索引为节点的key值,查询可以直接从这里查出信息;freqMap保存对应频率的链表信息,索引为节点的freq(频率),删除的时候可以快速从这里获取需要删除节点的信息。

/**
 * @param {number} capacity
 */
var LFUCache = function (capacity) {
  this.capacity = capacity;//最大缓存
  this.num = 0;//当前数目
  this.minFreq = 0;//当前最小频率
  this.kvMap = new Map();//保存节点信息
  this.freqMap = new Map();//保存对应频率的链表信息
};

6、编写get方法

get主要有以下两种情况:

(1)节点存在

  • 通过kvMap获取到对应节点
  • 将节点从freqMap中删除
  • 判断是否需要修改minFreq
  • 修改节点的freq
  • 重新将节点插入freqMap
  • 返回节点的value

(2)节点不存在
容量capacity为0或者kvMap中没有该节点信息,直接返回-1即可。

/**
 * @param {number} key
 * @return {number}
 */
LFUCache.prototype.get = function (key) {
  if (this.capacity === 0) return -1;
  if (!this.kvMap.has(key)) return -1;
  //通过kvMap获取到对应节点
  let node = this.kvMap.get(key);
  let linkLine = this.freqMap.get(node.freq);
  //将节点从freqMap中删除
  linkLine.removeNode(node);
  //判断是否需要修改minFreq
  //清空了
  if (linkLine.head.next === linkLine.tail) {
    this.freqMap.delete(node.freq);
    if (this.minFreq == node.freq) this.minFreq++;
  }
  //修改节点的freq
  node.freq++;
  //重新将节点插入freqMap
  if (this.freqMap.has(node.freq)) {
    linkLine = this.freqMap.get(node.freq);
    linkLine.addNode(node);
  } else {
    this.freqMap.set(node.freq, new LFULinkLine(node));
  }
  //返回节点的value
  return node.val;
};

7、编写put方法

put操作主要有以下两种情况:

(1)更新

  • 通过kvMap获取到对应节点
  • 将节点从freqMap中删除
  • 判断是否需要修改minFreq
  • 更新节点信息
  • 重新将节点插入freqMap

(2)存入

存入情况下又有两种情况:

容量已满

  • 通过minFreq找到需要删除的节点
  • 将节点从freqMap中删除
  • 判断是否需要修改minFreq
  • 将新节点插入freqMap
  • 更新minFreq

容量未满

  • 修改num
  • 将新节点插入freqMap
  • 更新minFreq
/**
 * @param {number} key
 * @param {number} value
 * @return {void}
 */
LFUCache.prototype.put = function (key, value) {
  if (this.capacity === 0) return;
  if (this.kvMap.has(key)) {
    //更新
    //通过kvMap获取到对应节点
    let node = this.kvMap.get(key);
    //将节点从freqMap中删除
    let linkLine = this.freqMap.get(node.freq);
    linkLine.removeNode(node);
    //判断是否需要修改minFreq
    if (linkLine.head.next === linkLine.tail) {
      if (this.minFreq == node.freq) this.minFreq++;
      this.freqMap.delete(node.freq);
    }
    //更新节点信息
    node.val = value;
    node.freq++;
    //重新将节点插入freqMap
    if (this.freqMap.has(node.freq)) {
      linkLine = this.freqMap.get(node.freq);
      linkLine.addNode(node);
    } else {
      linkLine = new LFULinkLine(node);
      this.freqMap.set(node.freq, linkLine);
    }
  } else {//存入
    if (this.capacity == this.num) {//存满
      //通过minFreq找到需要删除的节点
      let freq = this.minFreq;
      let linkLine = this.freqMap.get(freq);
      let node = linkLine.tail.pre;
      //将节点从freqMap中删除  
      linkLine.removeNode(node);
      this.kvMap.delete(node.key);
      //判断是否需要修改minFreq
      if (linkLine.head.next === linkLine.tail) {
        this.freqMap.delete(node.freq);
      }
    } else {
      //修改num
      this.num++;
    }
    //将新节点插入freqMap
    let node = new LFUNode(key, value, 0);
    this.kvMap.set(key, node);
    if (this.freqMap.has(0)) {
      let linkLine = this.freqMap.get(0);
      linkLine.addNode(node);
    } else {
      let linkLine = new LFULinkLine(node);
      this.freqMap.set(0, linkLine);
    }
    //更新minFreq
    this.minFreq = 0;
  }
};

代码

const LFUNode = function (
  key = "",
  val = "",
  freq = 0,
  pre = null,
  next = null
) {
  this.key = key;
  this.val = val;
  this.freq = freq;
  this.pre = pre;
  this.next = next;
};
const LFULinkLine = function (node) {
  let head = new LFUNode("head");
  let tail = new LFUNode("tail");
  head.next = node;
  tail.pre = node;
  node.pre = head;
  node.next = tail;
  this.head = head;
  this.tail = tail;
};
LFULinkLine.prototype.addNode = function (node) {
  this.head.next.pre = node;
  node.pre = this.head;
  node.next = this.head.next;
  this.head.next = node;
};
LFULinkLine.prototype.removeNode = function (node) {
  node.pre.next = node.next;
  node.next.pre = node.pre;
};

/**
 * @param {number} capacity
 */
var LFUCache = function (capacity) {
  this.capacity = capacity;
  this.num = 0;
  this.minFreq = 0;
  this.kvMap = new Map();
  this.freqMap = new Map();
};

/**
 * @param {number} key
 * @return {number}
 */
LFUCache.prototype.get = function (key) {
  if (this.capacity === 0) return -1;
  if (!this.kvMap.has(key)) return -1;
  let node = this.kvMap.get(key);
  let linkLine = this.freqMap.get(node.freq);
  linkLine.removeNode(node);
  //清空了
  if (linkLine.head.next === linkLine.tail) {
    this.freqMap.delete(node.freq);
    if (this.minFreq == node.freq) this.minFreq++;
  }
  node.freq++;
  if (this.freqMap.has(node.freq)) {
    linkLine = this.freqMap.get(node.freq);
    linkLine.addNode(node);
  } else {
    this.freqMap.set(node.freq, new LFULinkLine(node));
  }
  return node.val;
};

/**
 * @param {number} key
 * @param {number} value
 * @return {void}
 */
LFUCache.prototype.put = function (key, value) {
  if (this.capacity === 0) return;
  if (this.kvMap.has(key)) {
    //更新
    let node = this.kvMap.get(key);
    node.val = value;
    let linkLine = this.freqMap.get(node.freq);
    linkLine.removeNode(node);
    if (linkLine.head.next === linkLine.tail) {
      if (this.minFreq == node.freq) this.minFreq++;
      this.freqMap.delete(node.freq);
    }
    node.freq++;
    if (this.freqMap.has(node.freq)) {
      linkLine = this.freqMap.get(node.freq);
      linkLine.addNode(node);
    } else {
      linkLine = new LFULinkLine(node);
      this.freqMap.set(node.freq, linkLine);
    }
  } else {
    //存入
    if (this.capacity == this.num) {
      //存满
      let freq = this.minFreq;
      let linkLine = this.freqMap.get(freq);
      let node = linkLine.tail.pre;
      linkLine.removeNode(node);
      this.kvMap.delete(node.key);

      if (linkLine.head.next === linkLine.tail) {
        this.freqMap.delete(node.freq);
      }
    } else {
      this.num++;
    }
    let node = new LFUNode(key, value, 0);
    this.kvMap.set(key, node);
    if (this.freqMap.has(0)) {
      let linkLine = this.freqMap.get(0);
      linkLine.addNode(node);
    } else {
      let linkLine = new LFULinkLine(node);
      this.freqMap.set(0, linkLine);
    }
    this.minFreq = 0;
  }
};

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

到此这篇关于JavaScript双向链表实现LFU缓存算法的文章就介绍到这了,更多相关JavaScript LFU缓存算法内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 在js里怎么实现Xcode里的callFuncN方法(详解)

    本人使用的WebStorm编辑器,里面没有callFuncN, 不记得Lua是否支持callFuncN,如果不支持相信应该能用同样的方法做到. 废话不多说,贴代码: loadDown : function () { var dis = this.left_move.getPositionY() - this.left.getPositionY(); // 得到一个距离 var act1 = new cc.moveBy(0.5,cc.p(0,-dis)); var act2 = cc.callFu

  • JavaScript双向链表实现LFU缓存算法

    目录 什么是LFU 描述 解题思路 1.构造节点结构体 2.构造双向链表 3.编写链表头添加节点方法 4.编写删除节点方法 5.构造LRU缓存结构体 6.编写get方法 7.编写put方法 什么是LFU LFU(Least Frequently Used),最近最少使用策略,也就是说在一段时间内,数据被使用频次最少的,优先被淘汰.它是一种用于管理计算机内存的缓存算法,采用LFU算法的最简单方法是为每个加载到缓存的块分配一个计数器.每次引用该块时,计数器将增加一.当缓存达到容量并有一个新的内存块等

  • JavaScript双向链表实现LRU缓存算法的示例代码

    目录 目标 什么是LRU 简介 硬件支持 寄存器 栈 代码实现 思路 链表节点数据结构 链表数据结构 LRUCache数据结构 完整代码 测试 目标 请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构. 实现 LRUCache 类: LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存 int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 . void put(int key

  • C++ 实现LRU 与 LFU 的缓存算法

    目录 一.LRU (Least Recently Used) 缓存 二.LFU (Least Frequently Used) 缓存 一.LRU (Least Recently Used) 缓存 详见 LeetCode Q146 https:// leetcode.com/problems/l ru-cache/ https:// leetcode-cn.com/problem s/lru-cache/ 问题描述: LRUCache(int capacity) 以正整数作为容量 capacity

  • python基于双向链表实现LFU算法

    本文实例为大家分享了python实现LFU算法的具体代码,供大家参考,具体内容如下 在第一节中实现了双向链表DoubleLinkedList类,上一节中基于双向链表实现了LRU算法,本节课我们继续基于双向链表实现LFU(Least frequently used 最不经常使用)算法. 一.重写Node节点类 构建LFUNode类 继承自第一节中的Node类,添加freq属性用来表示节点使用频率 class LFUNode(Node):     def __init__(self, key, va

  • LRU LFU TinyLFU缓存算法实例详解

    目录 简介 一.LRU和LFU算法 LRU算法 LFU算法 小结: 二.TinyLFU 三.Window-TinyLFU 简介 前置知识 知道什么是缓存 听完本节公开课,你可以收获 掌握朴素LRU.LFU算法的思想以及源码 掌握一种流式计数的算法 Count-Min Sketch 手撕TinyLFU算法.分析Window-TinyLFU源码 一.LRU和LFU算法 LRU算法 LRU Least Recently Used 最近最少使用算法 LRU 算法的思想是如果一个数据在最近一段时间没有被访

  • JavaScript版的TwoQueues缓存模型

    本文所指TwoQueues缓存模型,是说数据在内存中的缓存模型. 无论何种语言,都可能需要把一部分数据放在内存中,避免重复运算.读取.最常见的场景就是JQuery选择器,有些Dom元素的选取是非常耗时的,我们希望能把这些数据缓存起来,不必每次调用都去重新遍历Dom树. 存就存吧,但总得有个量吧!总不能把所有的历史数据都放在内存中,毕竟目前内存的容量还是相当可怜的,就算内存够大,理论上每个线程分配的内存也是有限制的. 那么问题来了,如何才能高效的把真正有用的数据缓存起来呢?这就涉及到淘汰算法,需要

  • JS 实现缓存算法的示例(FIFO/LRU)

    FIFO 最简单的一种缓存算法,设置缓存上限,当达到了缓存上限的时候,按照先进先出的策略进行淘汰,再增加进新的 k-v . 使用了一个对象作为缓存,一个数组配合着记录添加进对象时的顺序,判断是否到达上限,若到达上限取数组中的第一个元素key,对应删除对象中的键值. /** * FIFO队列算法实现缓存 * 需要一个对象和一个数组作为辅助 * 数组记录进入顺序 */ class FifoCache{ constructor(limit){ this.limit = limit || 10 this

  • JavaScript 双向链表操作实例分析【创建、增加、查找、删除等】

    本文实例讲述了JavaScript 双向链表操作.分享给大家供大家参考,具体如下: 一个 双向链表(doubly linked list) 是由一组称为节点的顺序链接记录组成的链接数据结构.每个节点包含两个字段,称为链接,它们是对节点序列中上一个节点和下一个节点的引用 开始节点和结束节点的上一个链接和下一个链接分别指向某种终止节点,通常是前哨节点或null,以方便遍历列表.如果只有一个前哨节点,则列表通过前哨节点循环链接.它可以被概念化为两个由相同数据项组成的单链表,但顺序相反. class D

  • JavaScript实现树的遍历算法示例【广度优先与深度优先】

    本文实例讲述了JavaScript实现树的遍历算法.分享给大家供大家参考,具体如下: <script type="text/javascript"> var t = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19]; //下面这段深度优先搜索方法出自Aimingoo的[JavaScript语言精髓与编程实践] var deepView = function(aTree,iNode) { (iNode in aTree)

  • Javascript中的常见排序算法

    具体代码及比较如下所示: 复制代码 代码如下: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">  <html xmlns="http://www.w3.org/1999/xhtml" lang="gb2312">

随机推荐