LRU缓存设计
设计要求
运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put 。
获取数据 get(key) - 如果密钥 (key) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1。
写入数据 put(key, value) - 如果密钥不存在,则写入其数据值。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。
进阶:
你是否可以在 O(1) 时间复杂度内完成这两种操作?
思考
| 要求 | 满足条件 | 对应数据结构 |
|---|---|---|
| 查找快 | get时间复杂度为O(1) | 哈希表 |
| 插入快 | put时间复杂度为O(1) | 链表 |
| 有顺序之分 | 区分最近使用的和久未使用的数据 | 链表 |
| 删除快 | put时间复杂度为O(1) | 双向链表 |
综上分析,需要双向链表和哈希表结合,形成哈希链表。
实现
首先设计双向链表:
-
节点类CacheNode
1 2 3 4 5 6 7 8 9
class CacheNode { public var key : Int public var val : Int public var next, prev : CacheNode? init(_ k : Int, _ v : Int) { self.key = k self.val = v } }
-
双向链表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
class DoubleList { //头尾虚节点 var head, tail : CacheNode? //链表元素数 var size : Int = 0 init() { head = CacheNode(0, 0) tail = CacheNode(0, 0) head?.next = tail tail?.prev = head size = 0 } //在链表头部添加节点x public func addFirst(_ x : CacheNode) { x.next = head?.next x.prev = head head?.next?.prev = x head?.next = x size += 1 } //删除链表中的节点(x一定存在) public func remove(_ x : CacheNode) { x.prev?.next = x.next x.next?.prev = x.prev size -= 1 } //删除链表最后一个节点,并返回该节点 public func removeLast() -> CacheNode? { if tail?.prev?.val == (head?.val)! { return nil } let last = tail?.prev remove(last!) return last } //返回链表长度 public func Size() -> Int { return size } }
然后是设计哈希链表:
-
LRU类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
class LRUCache { //哈希表 var map: [Int : CacheNode?] = [:] //双向链表 var cache: DoubleList //最大容量 var cap: Int init(_ capacity: Int) { self.cap = capacity self.map = [:] self.cache = DoubleList() } func get(_ key: Int) -> Int { if map[key] == nil { return -1 } let val = (map[key] as? CacheNode)!.val //利用put方法把数据提前 put(key,val) return val } func put(_ key: Int, _ value: Int) { //先把新节点x做出来 let x = CacheNode(key, value) if map[key] != nil { //删除旧节点 cache.remove((map[key] as? CacheNode)!) } else { if cap == cache.Size() { //删除链表最后一个节点 let last = cache.removeLast() map.removeValue(forKey: (last?.key)!) } } //直接添加到头部 cache.addFirst(x) //更新map数据 map[key] = x } }
对于LRU类,有两个值得注意的点:
- 在get方法中,如果一个数据之前已经记录过,需要用put方法更新最近
- 在更新链表的同时,不要忘记更新map
本文由作者按照
CC BY 4.0
进行授权