如何实现时间复杂度为 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)
相关推荐
Cachel wood10 分钟前
python round四舍五入和decimal库精确四舍五入
java·linux·前端·数据库·vue.js·python·前端框架
Jasmine_llq10 分钟前
《 火星人 》
算法·青少年编程·c#
学代码的小前端12 分钟前
0基础学前端-----CSS DAY9
前端·css
joan_8516 分钟前
layui表格templet图片渲染--模板字符串和字符串拼接
前端·javascript·layui
迷雾漫步者19 分钟前
Flutter组件————PageView
flutter·跨平台·dart
闻缺陷则喜何志丹21 分钟前
【C++动态规划 图论】3243. 新增道路查询后的最短距离 I|1567
c++·算法·动态规划·力扣·图论·最短路·路径
Lenyiin40 分钟前
01.02、判定是否互为字符重排
算法·leetcode
m0_748236111 小时前
Calcite Web 项目常见问题解决方案
开发语言·前端·rust
鸽鸽程序猿1 小时前
【算法】【优选算法】宽搜(BFS)中队列的使用
算法·宽度优先·队列