是什么
最不经常使用算法(LFU): 这个缓存算法使用一个计数器来记录条目被访问的频率。通过使用LFU缓存算法,最低访问数的条目首先被移除。这个方法并不经常使用,因为它无法对一个拥有最初高访问率之后长时间没有被访问的条目缓存负责。leetcode真题地址
方法
LFUCache(capacity)
- 用数据结构的容量 capacity
初始化对象
get(key)
- 如果键 key
存在于缓存中,则获取键的值,否则返回 -1
put(key, value)
- 如果键 key
已存在,则变更其值;如果键不存在,请插入键值对。当缓存达到其容量 capacity 时,则应该在插入新项之前,移除最不经常使用的项。在此问题中,当存在平局(即两个或更多个键具有相同使用频率)时,应该去除 最近最久未使用 的键。
流程拆解
我们需要 map
和双向链表的结构以达到 get
和 put
的时间复杂度为 O(1)
nodeMap
:用于存储节点信息
freqMap
:按使用频率存储双向链表结构,方便定位
当我们调用 put(1,1)
方法时是这样的,将其作为插入到 key = 1
双向链表中的第一个元素,然后更新 minFreq
为 1
put(2,2)
:将其作为插入到 key = 1
双向链表中的第一个元素,然后更新 minFreq
为 1
get(1)
:此时通过 nodeMap
中获取值,然后更新 freqMap
中的 key = 2
双向链表
put(3,3)
:此时因为个数已经满了,需要删除最不经常使用的元素,通过 minFreq
去定位找到目标链表,并取出最后一个元素
将 node-2
删除后,将 node-3
插入,并更新 minFreq
为 1
代码实现
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 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
|
function ListNode(k, v, c, l, r) { this.k = k this.v = v this.c = c || 1 this.l = l this.r = r }
function Linklist() { this.head = new ListNode(-1, 'head') this.tail = new ListNode(-1, 'tail') this.head.r = this.tail this.tail.l = this.head this.length = 0 }
Linklist.prototype.add = function (node) { let r = this.head.r node.r = r node.l = this.head r.l = node this.head.r = node this.length++ }
Linklist.prototype.delete = function (node) { if (node.l) { let l = node.l l.r = node.r node.r.l = l } this.length-- }
var LFUCache = function (capacity) { this.capacity = capacity this.minFreq = 1 this.nodeMap = new Map() this.freqMap = new Map() }
LFUCache.prototype.get = function (key) { if (this.capacity === 0) return -1 const node = this.nodeMap.get(key) if (!node) return -1 this.update(node) return node.v }
LFUCache.prototype.update = function (node) { let last = this.freqMap.get(node.c) last.delete(node) if (!last.length && node.c === this.minFreq) { this.minFreq++ } let now = this.freqMap.get(++node.c) if (!now) { now = new Linklist() this.freqMap.set(node.c, now) } now.add(node) }
LFUCache.prototype.put = function (key, value) { if (this.capacity === 0) return let node = this.nodeMap.get(key) if (!node) { node = new ListNode(key, value) if (!this.freqMap.get(node.c)) { this.freqMap.set(node.c, new Linklist()) } const nodes = this.freqMap.get(node.c) if (this.nodeMap.size >= this.capacity) { let minNodes = this.freqMap.get(this.minFreq) let minNode = minNodes.tail.l this.nodeMap.delete(minNode.k) minNodes.delete(minNode) } this.minFreq = 1 this.nodeMap.set(key, node) nodes.add(node) } else { node.v = value this.update(node) } }
|
缺点
存在历史数据影响将来数据的”缓存污染”问题,累计的数据越多,对新加的数据可能起不到缓存的作用。比如历史记录中前 9
条数据都是 100+
的次数,后面新数据上来,使用次数赶不上历史数据时会优先被淘汰。
无法对一个拥有最初高访问率之后长时间没有被访问的条目缓存负责
源码地址