[TypeScript]手撸LRU

[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();

  • 8
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值