[TypeScript]手撸LRU

news2024/7/31 6:30:01

[TypeScript]手撸LRU

流程图

容量未满
容量已满
存在
不存在
存在
不存在
容量未满
容量已满
开始
检查缓存容量
创建新节点
删除最久未使用节点
从双向链表中删除节点
从键到节点映射中删除对应键
插入新节点到双向链表末尾
更新键到节点映射
接收get请求
检查键是否存在
从双向链表中删除节点
返回-1
更新节点到双向链表末尾
更新键到节点映射
返回节点值
接收put请求
检查键是否存在
更新节点值
检查缓存容量
移动节点到双向链表末尾
更新键到节点映射
结束

思路

LRU是一种常见的缓存淘汰策略,它淘汰最长时间未被使用的元素。下面是代码实现的详细思路:

  1. 双向链表节点(DoubleLinkNode

    • 包含键(key)和值(val)。
    • 每个节点还包含指向前一个节点(prev)和后一个节点(next)的指针。
  2. 双向链表(DoubleLink

    • 包含链表的大小(size)和指向头节点(head)与尾节点(tail)的指针。
    • 头节点和尾节点是虚拟节点,用于简化链表的插入和删除操作。
    • 提供添加到末尾(addLast)、删除节点(remove)和删除首个节点(removeFirst)的方法。
    • 提供打印链表内容的方法(print),用于调试。
  3. LRU缓存(LRUCache

    • 包含缓存的容量(capacity)。
    • 一个键到节点的映射(keyToNodeMap),用于快速查找缓存中的元素。
    • 一个双向链表(doubleLink),用于存储缓存中的元素,并按照最近最少使用的原则进行排序。
  4. get 方法

    • 检查键是否存在于键到节点映射中。
    • 如果存在,从双向链表中删除该节点,并更新到链表末尾,表示该元素最近被访问。
    • 返回节点的值。
  5. put 方法

    • 如果键已存在,更新节点的值,并将其移动到链表末尾。
    • 如果键不存在且缓存未满,创建新节点并将其添加到链表末尾。
    • 如果键不存在且缓存已满,删除双向链表中的第一个节点(最久未使用的节点),然后添加新节点。
  6. updateNode 方法

    • 私有方法,用于将节点添加到双向链表的末尾,表示节点最近被访问。
  7. print 方法

    • 打印键到节点映射和双向链表的内容,用于调试。

这种实现方式的优点是:

  • 通过双向链表,可以快速地在头部或尾部添加和删除节点,时间复杂度为O(1)。
  • 通过键到节点的映射,可以快速地查找缓存中的元素,时间复杂度也为O(1)。

LRU缓存适用于需要快速访问最近频繁使用的数据的场景,例如网页缓存、数据库查询缓存等。

代码

class DoubleLinkNode {
    key: number;
    val: number;
    next: DoubleLinkNode;
    prev: DoubleLinkNode;

    constructor(key, val) {
        this.key = key;
        this.val = val;
    }
}

class DoubleLink {
    size: number;
    head: DoubleLinkNode;
    tail: DoubleLinkNode;

    constructor() {
        this.size = 0;
        this.head = new DoubleLinkNode(-1, -1);
        this.tail = new DoubleLinkNode(-1, -1);
        this.head.next = this.tail;
        this.tail.prev = this.head;
    }

    addLast(node) {
        // 获取倒数第二个节点
        const lastSecondNode = this.tail.prev;

        // 插入到倒数第二个节点后面
        lastSecondNode.next = node;
        this.tail.prev = node;

        node.prev = lastSecondNode;
        node.next = this.tail;

        this.size++;
    }

    remove(node) {
        // 因为前后有head和tail兜着,在链表中间直接删除即可
        const prev = node.prev;
        const next = node.next;
        prev.next = next;
        next.prev = prev;
        this.size--;
    }

    // 删除首个节点并返回
    removeFirst() {
        if (this.size < 1) {
            return null;
        }

        const first = this.head.next;
        this.remove(first);
        return first;
    }

    print() {
        let res = 'head';
        let cur = this.head.next;
        while (cur !== this.tail) {
            res = `${res} -> {${cur.key} , ${cur.val}}`;
            cur = cur.next;
        }
        console.log('res: ', res);
    }
}

class LRUCache {
    capacity: number;
    keyToNodeMap: Map<number, DoubleLinkNode>;
    doubleLink: DoubleLink;

    constructor(capacity: number) {
        this.capacity = capacity;
        this.keyToNodeMap = new Map();
        this.doubleLink = new DoubleLink();
    }

    get(key: number): number {
        if (!this.keyToNodeMap.has(key)) {
            return -1;
        }

        // 获取key对应的节点
        const node = this.keyToNodeMap.get(key);

        // 删除这个节点并更新到最后
        this.doubleLink.remove(node);
        this.updateNode(node);

        return node.val;
    }

    put(key: number, value: number): void {
        // 如果是已有的值,只做更新操作
        if (this.keyToNodeMap.has(key)) {
            const curNode = this.keyToNodeMap.get(key);
            curNode.val = value;

            this.doubleLink.remove(curNode);
            this.updateNode(curNode);
            return;
        }

        // 容量是否满了
        if (this.doubleLink.size >= this.capacity) {
            // 删除最后使用的节点
            const deleteNode = this.doubleLink.removeFirst();

            this.keyToNodeMap.delete(deleteNode.key);
        }

        // 开始插入
        const newNode = new DoubleLinkNode(key, value);
        this.keyToNodeMap.set(key, newNode);
        this.updateNode(newNode);
    }

    // 插入或读取时,将这个节点插入到最后
    private updateNode(node) {
        this.doubleLink.addLast(node);
    }

    // log调试方法
    print() {
        console.log('\n\n--------------------------');
        console.log('keyToNodeMap: ', this.keyToNodeMap);
        this.doubleLink.print();
        console.log('--------------------------');
    }
}

测试用例


const main2 = () => {
    //   ["LFUCache","put","put","get","put","get","get","put","get","get","get"]
    // [[2],[1,1],[2,2],[1],[3,3],[2],[3],[4,4],[1],[3],[4]]
    // 输出: [null, null, null, 1, null, -1, 3, null, -1, 3, 4]

    // 构造一个容量为 2 的 LFU 缓存
    const cache = new LRUCache(2);
    cache.print();

    cache.put(1, 1); // 缓存是 {1=1}
    cache.put(2, 2); // 缓存是 {1=1, 2=2}

    const res1 = cache.get(1); // 返回 1
    cache.print();
    console.log('res1: ', res1);

    cache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}
    const res2 = cache.get(2); // 返回 -1 (未找到)
    console.log('res2: ', res2);
    cache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3}

    const res3 = cache.get(1); // 返回 -1 (未找到)
    console.log('res3: ', res3);
    const res4 = cache.get(3); // 返回 3
    console.log('res4: ', res4);
    const res5 = cache.get(4); // 返回 4
    console.log('res5: ', res5);

    cache.print();
};

// main2();

const main3 = () => {
    // ["LRUCache","get","put","get","put","put","get","get"]
    // [[2],[2],[2,6],[1],[1,5],[1,2],[1],[2]]
    // 预期结果:[null,-1,null,-1,null,null,2,6]

    const cache = new LRUCache(2);
    console.log('cache.get(2): ', cache.get(2));

    cache.put(2, 6);

    console.log('cache.get(1): ', cache.get(1));

    cache.put(1, 5);
    cache.put(1, 2);

    console.log('cache.get(1): ', cache.get(1));

    cache.print();
    console.log('cache.get(2): ', cache.get(2));
};

main3();

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1918331.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

第十九章 Nest multer 文件上传

上章我们了解了Express multer 文件上传的相关操作 本章将了解Nest中的文件上传。用 multer 包处理 multipart/form-data 类型的请求中的 file 新建个 nest 项目: nest new nest-multer-upload 安装 multer 的 ts 类型的包&#xff1a; npm install -D types/multer1、单文件…

Cesium中实现图层组

图层组 某天领导找我&#xff0c;说业务中可能存在多个影像服务为一个图层组&#xff0c;并且需要同时加载和同时在图层列表中上下移动的需求。 例如一些专题地图&#xff0c;包含所有学校、医院、公交站等图层&#xff0c;而这些图层都是单独发布的。 在 Cesium 中确实存在…

响应式建站公司企业官网源码系统 带源代码以及搭建部署教程

系统概述 响应式建站公司企业官网源码系统是一套集设计、开发、部署于一体的综合性解决方案。它旨在为企业提供一个易于定制、功能强大、适应各种设备屏幕的官方网站平台。 该系统采用先进的技术架构&#xff0c;确保网站的稳定性和性能。它能够与各种后端数据库和服务器环境…

爱秀国际英语公信力怎么样?靠谱吗?

同爱秀国际英语公信力怎么样&#xff1f; ①爱秀国际英语成立于09年&#xff0c;已经有15年的教学积累&#xff0c;专门针对大学生研发的英语口语课程。 ②历年来不仅教学效果显著&#xff0c;在社会上也获得过很多荣誉&#xff0c;在历年的教育大会上也荣获过诸多认可&…

一招杜绝 | 网站被劫持强制植入广告的问题

在我们日常上网过程中&#xff0c;经常会遇到打开一个网页&#xff0c;网页都还没有显示出来&#xff0c;一堆广告就弹出来的现象。或者网页刚刚打开&#xff0c;没几分钟 就会弹出来某游戏广告&#xff0c;注册领豪华坐骑等等的小广告。这些广告不仅仅会让我们对网站的真实性产…

eplan软件许可优化解决方案

Eplan软件介绍 Eplan是一款专业的电气设计软件&#xff0c;用于自动化工程和电气系统的设计与文档化。它由德国的Eplan Software & Service GmbH开发&#xff0c;并在全球范围内广泛应用于工程设计和电气工程领域。 Eplan软件提供了全面的工具和功能&#xff0c;以简化和优…

202-509SF 同轴连接器

型号简介 202-509SF是Southwest Microwave的连接器。这款连接器机身和法兰由不锈钢合金 UNS-30300 制成&#xff0c;螺纹接头则采用 5C360 黄铜合金。接触点采用 BeCu 合金&#xff0c;并经过镀金处理&#xff0c;以提供优异的导电性和耐腐蚀性。绝缘体则由 PTFE 氟碳或 ULTEM …

3.动态规划.基础

3.动态规划.基础 基础理论背包基础理论01背包完全背包多重背包 题目1.斐波那契数2.爬楼梯3.使用最小花费爬楼梯4.不同路径5.不同路径2 基础理论 动态规划&#xff0c;英文&#xff1a;Dynamic Programming&#xff0c;简称DP&#xff0c;如果某一问题有很多重叠子问题&#xf…

外卖霸王餐系统有什么推荐的

​ 在当今数字化的商业环境中&#xff0c;各种创新的营销策略层出不穷&#xff0c;其中微客云霸王餐系统以其独特的商业模式和营销策略&#xff0c;受到了众多商家的青睐。该系统不仅为商家提供了一个高效的营销工具&#xff0c;还通过一系列的功能和优势&#xff0c;帮助商家…

Qt QChart 曲线图表操作

学习目标&#xff1a;QChart 曲线图表操作 学习内容 QT中的QChart类提供了一个功能强大的图表绘制框架,可以根据需求方便高效地绘制各种类型的图表,主要特点如下: 支持多种常见图表类型,如线图、条形图、饼图、散点图等各种类型。开发者只需要选择合适的图表类和数据即可绘制…

Android APT实战

Android开发中,注解平时我们用的比较多,也许我们会比较好奇,注解的背后是如何工作的,这篇文章帮大家一步步创建一个简单的注解处理器。 简介 APT(Annotation Processing Tool)即注解处理器,在编译的时候可以处理注解然后搞一些事情,也可以在编译时生成一些文件之类的。…

【Linux】常见指令收官权限理解

tar指令 上一篇博客已经介绍了zip/unzip指令&#xff0c;接下来我们来看一下另一个关于压缩和解压的指令&#xff1a;tar指令tar指令&#xff1a;打包/解包&#xff0c;不打开它&#xff0c;直接看内容 关于tar的指令有太多了&#xff1a; tar [-cxtzjvf] 文件与目录 ...…

2.4G芯片开发的遥控玩具方案介绍 东莞酷得

玩具从早期的简单功能&#xff0c;到现如今各种各样的智能操作&#xff0c;发展的速度也是飞速的。随着玩具市场的逐步完善与推进&#xff0c;中国的智能玩具市场也出现了很多远程遥控玩具。遥控玩具也是从最初的有线到现在的无线&#xff0c;从地上跑的到天上飞的&#xff0c;…

jmeter分布式(四)

一、gui jmeter的gui主要用来调试脚本 1、先gui创建脚本 先做一个脚本 演示&#xff1a;如何做混合场景的脚本&#xff1f; 用211的业务比例 ①启动数据库服务 数据库服务&#xff1a;包括mysql、redis mysql端口默认3306 netstat -lntp | grep 3306处于监听状态&#xf…

LeetCode 88.合并两个有序数组 C写法

LeetCode 88.合并两个有序数组 C写法 思路&#xff1a; ​ 由题nums1的长度为mn&#xff0c;则我们不需要开辟新的数组去存储元素。题目要求要有序合并&#xff0c;于是可以判断哪边数更大&#xff0c;将更大的数尾插在nums1中。 ​ 定义三个变量来控制下标&#xff0c;end1控…

Linux--线程ID封装管理原生线程

目录 1.线程的tid&#xff08;本质是线程属性集合的起始虚拟地址&#xff09; 1.1pthread库中线程的tid是什么&#xff1f; 1.2理解库 1.3phtread库中做了什么&#xff1f; 1.4线程的tid&#xff0c;和内核中的lwp 1.5线程的局部存储 2.封装管理原生线程库 1.线程的tid…

四川赤橙宏海商务信息咨询有限公司抖音电商服务靠谱吗?

在数字化浪潮席卷全球的今天&#xff0c;电商行业蓬勃发展&#xff0c;各种新兴电商平台层出不穷。其中&#xff0c;抖音电商以其独特的社交属性和庞大的用户基础&#xff0c;迅速崛起为行业新星。四川赤橙宏海商务信息咨询有限公司&#xff0c;作为专注于抖音电商服务的佼佼者…

自动编码器(Autoencoders)

在“深度学习”系列中&#xff0c;我们不会看到如何使用深度学习来解决端到端的复杂问题&#xff0c;就像我们在《A.I. Odyssey》中所做的那样。我们更愿意看看不同的技术&#xff0c;以及一些示例和应用程序。 1、引言 ① 什么是自动编码器&#xff08;AutoEncoder&#xff…

【js/ts】js/ts高精度加减乘除函数

加法 /*** 高精度加法函数&#xff0c;处理字符串或数字输入&#xff0c;去除尾部多余的零* param {string|number} a - 被加数* param {string|number} b - 加数* returns {string} - 计算结果&#xff0c;去除尾部多余的零* throws {Error} - 如果输入不是有效的数字&#x…

油罐车的罐体结构介绍

油罐车的罐体一般用优质低碳钢板制成&#xff0c;罐内被隔板分为前、后两部分&#xff0c;相互隔离。每个舱内部均配备一道防波板&#xff0c;以加强罐体的稳定性并减缓行驶中油料对罐体的冲击。其车身由车架、车厢等组成&#xff0c;车架是整个油罐车的骨架&#xff0c;承载着…