如何实现时间复杂度为 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)
相关推荐
CoovallyAIHub4 分钟前
单目深度估计重大突破:无需标签,精度超越 SOTA!西湖大学团队提出多教师蒸馏新方案
深度学习·算法·计算机视觉
袁煦丞4 分钟前
【局域网秒传神器】LocalSend:cpolar内网穿透实验室第418个成功挑战
前端·程序员·远程工作
火柴就是我4 分钟前
每日见闻之Container Decoration
android·flutter
江城开朗的豌豆5 分钟前
Vuex数据突然消失?六招教你轻松找回来!
前端·javascript·vue.js
CoovallyAIHub7 分钟前
从FCOS3D到PGD:看深度估计如何快速搭建你的3D检测项目
深度学习·算法·计算机视觉
好奇心笔记16 分钟前
ai写代码随机拉大的,所以我准备给AI出一个设计规范
前端·javascript
江城开朗的豌豆16 分钟前
Vue状态管理进阶:数据到底是怎么"跑"的?
前端·javascript·vue.js
用户214118326360216 分钟前
dify案例分享-Dify v1.6.0 重磅升级:双向 MCP 协议引爆 AI 生态互联革命
前端
程序员海军17 分钟前
AI领域又新增协议: AG-UI
前端·openai·agent
我想说一句19 分钟前
React待办事项开发记:Hook魔法与组件间的悄悄话
前端·javascript·前端框架