程序员进步小妙招 之LRU算法

121 阅读2分钟

LRU 缓存算法实现:使用 TypeScript

在计算机科学中,缓存是一种用于临时存储数据的技术,以便在需要时可以更快地访问数据。其中一种常见的缓存策略是 最近最少使用 (LRU)  算法。本文将介绍 LRU 缓存算法的基本概念,并使用 TypeScript 语言实现一个简单的 LRU 缓存。

LRU 缓存算法简介

LRU 算法的核心思想是:当缓存达到最大容量时,最近最少使用的项将被移除以便为新项腾出空间。为了实现这一策略,我们需要一个数据结构来跟踪缓存中的数据访问顺序。

数据结构

为了实现 LRU 缓存,我们将使用以下两种数据结构:

  1. 双向链表:用于存储缓存项并按访问顺序排列。
  2. 哈希表:用于在 O(1) 时间内查找缓存项。

双向链表节点定义

首先,我们需要定义双向链表节点,它将包含键、值和指向前后节点的指针。

class ListNode {
  key: number;
  value: number;
  prev: ListNode | null;
  next: ListNode | null;

  constructor(key: number, value: number) {
    this.key = key;
    this.value = value;
    this.prev = null;
    this.next = null;
  }
}

LRU 缓存实现

接下来,我们实现 LRU 缓存类,它将包含以下方法:

  • constructor(capacity: number):初始化缓存的容量。
  • get(key: number): 获取给定键的值,如果键不存在,则返回 -1。
  • put(key: number, value: number): 更新给定键的值,如果键不存在,则插入一个新的键值对。如果缓存已满,需要移除最近最少使用的项。
class LRUCache {
  capacity: number;
  size: number;
  cache: Map<number, ListNode>;
  head: ListNode;
  tail: ListNode;

  constructor(capacity: number) {
    this.capacity = capacity;
    this.size = 0;
    this.cache = new Map();
    this.head = new ListNode(-1, -1);  // dummy head node
    this.tail = new ListNode(-1, -1);  // dummy tail node
    this.head.next = this.tail;
    this.tail.prev = this.head;
  }

  get(key: number): number {
    if (this.cache.has(key)) {
      const node = this.cache.get(key)!;
      this.moveToFront(node);
      return node.value;
    }
    return -1;
  }

  put(key: number, value: number): void {
    if (this.cache.has(key)) {
      const node = this.cache.get(key)!;
      node.value = value;
      this.moveToFront(node);
    } else {
      const newNode = new ListNode(key, value);
      this.cache.set(key, newNode);
      this.insertToFront(newNode);
      this.size++;

      if (this.size > this.capacity) {
        const lastNode = this.removeLast();
        this.cache.delete(lastNode.key);
        this.size--;
      }
    }
  }

  // Helper methods for operating on the doubly linked list

  private moveToFront(node: ListNode): void {
    this.removeNode(node);
    this.insertToFront(node);
  }

  private removeNode(node: ListNode): void {
    node.prev!.next = node.next;
    node.next!.prev = node.prev;
  }

  private insertToFront(node: ListNode): void {
    node.next = this.head.next;
    node.prev = this.head;
    this.head.next!.prev = node;
    this.head.next = node;
  }

  private removeLast(): ListNode {
    const lastNode = this.tail.prev!;
    this.removeNode(lastNode);
    return lastNode;
  }
}

示例

以下是使用我们的 LRU 缓存实现的几个示例:

示例 1:

const lruCache = new LRUCache(2);
lruCache.put(1, 1);
lruCache.put(2, 2);
console.log(lruCache.get(1)); // 输出 1
lruCache.put(3, 3);
console.log(lruCache.get(2)); // 输出 -1
lruCache.put(4, 4);
console.log(lruCache.get(1)); // 输出 -1
console.log(lruCache.get(3)); // 输出 3
console.log(lruCache.get(4)); // 输出 4

在这个示例中,我们创建了一个容量为 2 的 LRU 缓存。我们首先插入键值对 (1, 1) 和 (2, 2),然后使用 get 方法获取键 1 的值,它存在于缓存中,因此返回 1。接下来,我们插入键值对 (3, 3),这将导致键 2 被移除,因为它是最近最少使用的项。然后,我们插入键值对 (4, 4),这将导致键 1 被移除,因为它是最近最少使用的项。最后,我们使用 get 方法获取键 1、3 和 4 的值,它们在缓存中分别对应值 -1、3 和 4。