如何实现时间复杂度为 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)
相关推荐
前端不太难7 小时前
从 Navigation State 反推架构腐化
前端·架构·react
前端程序猿之路7 小时前
Next.js 入门指南 - 从 Vue 角度的理解
前端·vue.js·语言模型·ai编程·入门·next.js·deepseek
大布布将军7 小时前
⚡️ 深入数据之海:SQL 基础与 ORM 的应用
前端·数据库·经验分享·sql·程序人生·面试·改行学it
川贝枇杷膏cbppg8 小时前
Redis 的 RDB 持久化
前端·redis·bootstrap
子春一28 小时前
Flutter 2025 性能工程体系:从启动优化到帧率稳定,打造丝滑、省电、低内存的极致用户体验
flutter·ux
JIngJaneIL8 小时前
基于java+ vue农产投入线上管理系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot
LYFlied8 小时前
【每日算法】LeetCode 153. 寻找旋转排序数组中的最小值
数据结构·算法·leetcode·面试·职场和发展
唐装鼠8 小时前
rust自动调用Deref(deepseek)
开发语言·算法·rust
子春一28 小时前
Flutter 2025 性能工程体系:从启动优化到帧率稳定,打造丝滑如原生的用户体验
flutter·ux
天外天-亮8 小时前
v-if、v-show、display: none、visibility: hidden区别
前端·javascript·html