如何实现时间复杂度为 O(1)的LRU 缓存淘汰算法(附Dart及Swift代码实现)

原理

实现一个时间复杂度为 O(1) 的 LRU (Least Recently Used, 最近最少使用) 缓存淘汰算法,我们可以使用散列表(哈希表)和双向链表的组合。散列表用于快速查找,而双向链表用于维护缓存项的顺序。 散列表可以让我们在 O(1) 时间复杂度内快速查找缓存项,但它并不能直接告诉我们哪些缓存项是最久未使用的。这就是为什么我们需要使用双向链表来维护缓存项的访问顺序。

具体的实现步骤:

  1. 双向链表:

    • 每个节点包含 keyvalueprevnext 指针。
    • 双向链表的头部表示最近使用的缓存项,尾部表示最久未使用的缓存项。
  2. 散列表:

    • 散列表用于快速查找,键是缓存项的 key,值是指向双向链表中对应节点的引用。
  3. 操作:

    • 当访问一个缓存项时,如果该缓存项已经在链表中,则将其移动到链表头部。
    • 当插入一个新的缓存项时,如果缓存已满,则移除链表尾部的节点,并从散列表中删除对应的条目。
    • 新的缓存项总是插入到链表头部。

Dart代码实现

ini 复制代码
class Node {
  int key;
  int value;
  Node? next;
  Node? prev;

  Node({required this.key, required this.value, this.next, this.prev});
}

class LRUCache {
  int capacity;
  int size = 0;
  Node head;
  Node tail;
  Map<int, Node> cache;

  LRUCache({required this.capacity})
      : cache = {},
        head = Node(key: 0, value: 0, next: null, prev: null),
        tail = Node(key: 0, value: 0, next: null, prev: null) {
    head.next = tail;
    tail.prev = head;
  }

  int get(int key) {
    if (!cache.containsKey(key)) {
      return -1;
    }
    moveToHead(cache[key]!);
    return cache[key]!.value;
  }

  void put(int key, int value) {
    if (cache.containsKey(key)) {
      cache[key]!.value = value;
      moveToHead(cache[key]!);
    } else {
      Node node = Node(key: key, value: value);
      cache[key] = node;
      addToHead(node);
      size++;

      if (size > capacity) {
        Node removed = removeTail();
        cache.remove(removed.key);
        size--;
      }
    }
  }

  void moveToHead(Node node) {
    removeNode(node);
    addToHead(node);
  }

  void addToHead(Node node) {
    node.next = head.next;
    node.prev = head;
    head.next?.prev = node;
    head.next = node;
  }

  void removeNode(Node node) {
    node.prev?.next = node.next;
    node.next?.prev = node.prev;
  }

  Node removeTail() {
    Node? removed = tail.prev;
    removeNode(removed!);
    return removed;
  }
}

测试用例

scss 复制代码
void main() {
  LRUCache lruCache = LRUCache(capacity: 2);

  lruCache.put(1, 1);
  lruCache.put(2, 2);
  print(lruCache.get(1)); // 输出 1
  lruCache.put(3, 3);    // 移除键 2
  print(lruCache.get(2)); // 输出 -1 (因为键 2 已经被移除)
  lruCache.put(4, 4);    // 移除键 1
  print(lruCache.get(1)); // 输出 -1 (因为键 1 已经被移除)
  print(lruCache.get(3)); // 输出 3
  print(lruCache.get(4)); // 输出 4
}

Swift代码实现

swift 复制代码
class Node {
    var key: Int
    var value: Int
    var next: Node?
    var prev: Node?

    init(key: Int, value: Int, next: Node? = nil, prev: Node? = nil) {
        self.key = key
        self.value = value
        self.next = next
        self.prev = prev
    }
}

class LRUCache {
    private var capacity: Int
    private var size: Int = 0
    private var head: Node
    private var tail: Node
    private var cache: [Int: Node] = [:]

    init(capacity: Int) {
        self.capacity = capacity
        self.head = Node(key: 0, value: 0)
        self.tail = Node(key: 0, value: 0)
        self.head.next = self.tail
        self.tail.prev = self.head
    }

    func get(_ key: Int) -> Int {
        guard let node = cache[key] else {
            return -1
        }
        moveToHead(node)
        return node.value
    }

    func put(_ key: Int, _ value: Int) {
        if let node = cache[key] {
            node.value = value
            moveToHead(node)
        } else {
            let node = Node(key: key, value: value)
            cache[key] = node
            addToHead(node)
            size += 1

            if size > capacity {
                let removedNode = removeTail()
                cache.removeValue(forKey: removedNode.key)
                size -= 1
            }
        }
    }

    private func moveToHead(_ node: Node) {
        removeNode(node)
        addToHead(node)
    }

    private func addToHead(_ node: Node) {
        let nextNode = head.next
        head.next = node
        node.prev = head
        node.next = nextNode
        nextNode?.prev = node
    }

    private func removeNode(_ node: Node) {
        let prevNode = node.prev
        let nextNode = node.next
        prevNode?.next = nextNode
        nextNode?.prev = prevNode
    }

    private func removeTail() -> Node {
        let removedNode = tail.prev!
        removeNode(removedNode)
        return removedNode
    }
}

测试用例

scss 复制代码
// 测试用例 1: 初始化和基本操作
let cache = LRUCache(capacity: 2)
cache.put(1, 1)
cache.put(2, 2)
XCTAssertEqual(cache.get(1), 1)
cache.put(3, 3)
XCTAssertEqual(cache.get(2), -1)
cache.put(4, 4)
XCTAssertEqual(cache.get(1), -1)
XCTAssertEqual(cache.get(3), 3)
XCTAssertEqual(cache.get(4), 4)

// 测试用例 2: 缓存容量满时的操作
let cache2 = LRUCache(capacity: 2)
cache2.put(1, 1)
cache2.put(2, 2)
XCTAssertEqual(cache2.get(1), 1)
cache2.put(3, 3)
XCTAssertEqual(cache2.get(2), -1)
XCTAssertEqual(cache2.get(3), 3)

// 测试用例 3: 重复 put 操作
let cache3 = LRUCache(capacity: 2)
cache3.put(1, 1)
cache3.put(2, 2)
cache3.put(1, 3)
XCTAssertEqual(cache3.get(1), 3)
XCTAssertEqual(cache3.get(2), 2)

// 测试用例 4: 缓存为空时的操作
let cache4 = LRUCache(capacity: 2)
XCTAssertEqual(cache4.get(1), -1)
cache4.put(1, 1)
XCTAssertEqual(cache4.get(1), 1)
相关推荐
一只大侠的侠4 小时前
Flutter开源鸿蒙跨平台训练营 Day 10特惠推荐数据的获取与渲染
flutter·开源·harmonyos
崔庆才丨静觅6 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
你撅嘴真丑7 小时前
第九章-数字三角形
算法
passerby60617 小时前
完成前端时间处理的另一块版图
前端·github·web components
uesowys7 小时前
Apache Spark算法开发指导-One-vs-Rest classifier
人工智能·算法·spark
掘了7 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅7 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
ValhallaCoder7 小时前
hot100-二叉树I
数据结构·python·算法·二叉树
董董灿是个攻城狮7 小时前
AI 视觉连载1:像素
算法
崔庆才丨静觅8 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端