原理
实现一个时间复杂度为 O(1) 的 LRU (Least Recently Used, 最近最少使用) 缓存淘汰算法,我们可以使用散列表(哈希表)和双向链表的组合。散列表用于快速查找,而双向链表用于维护缓存项的顺序。 散列表可以让我们在 O(1) 时间复杂度内快速查找缓存项,但它并不能直接告诉我们哪些缓存项是最久未使用的。这就是为什么我们需要使用双向链表来维护缓存项的访问顺序。
具体的实现步骤:
-
双向链表:
- 每个节点包含
key
、value
、prev
和next
指针。 - 双向链表的头部表示最近使用的缓存项,尾部表示最久未使用的缓存项。
- 每个节点包含
-
散列表:
- 散列表用于快速查找,键是缓存项的
key
,值是指向双向链表中对应节点的引用。
- 散列表用于快速查找,键是缓存项的
-
操作:
- 当访问一个缓存项时,如果该缓存项已经在链表中,则将其移动到链表头部。
- 当插入一个新的缓存项时,如果缓存已满,则移除链表尾部的节点,并从散列表中删除对应的条目。
- 新的缓存项总是插入到链表头部。
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)