如何实现时间复杂度为 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 分钟前
用自写的jQuery库+Ajax实现了省市联动
java·前端·javascript·ajax·jquery
元亓亓亓11 分钟前
LeetCode热题100--206.反转链表--简单
算法·leetcode·链表
coderYYY15 分钟前
多个el-form-item两列布局排齐且el-select/el-input组件宽度撑满
前端·javascript·vue.js·elementui·前端框架
诚丞成26 分钟前
BFS算法篇——从晨曦到星辰,BFS算法在多源最短路径问题中的诗意航行(上)
java·算法·宽度优先
hongjianMa26 分钟前
2024睿抗编程赛国赛-题解
算法·深度优先·图论·caip
荔枝吖40 分钟前
项目中会出现的css样式
前端·css·html
Dontla43 分钟前
何时需要import css文件?怎么知道需要导入哪些css文件?为什么webpack不提示CSS导入?(导入css导入规则、css导入规范)
前端·css·webpack
czy87874751 小时前
两种常见的C语言实现64位无符号整数乘以64位无符号整数的实现方法
c语言·算法
小堃学编程1 小时前
前端学习(2)—— CSS详解与使用
前端·css·学习
yzx9910131 小时前
支持向量机案例
算法·机器学习·支持向量机