LFU:
採用LFU算法的最簡單方法是爲每個加載到緩存的快分配一個計數器.每次引用該塊時,計數器將增加1.當緩存達到容量並且有一個新塊等待插入時,系統將搜索計數器最低的塊並將其從緩存中刪除
實現: LFU的每個數據塊都有一個引用計數,所有數據塊按照引用計數排序,具有相同引用計數的數據塊則按照時間排序
- 新加入數據插入到隊列尾部(因爲引用計數爲1)
- 隊列中的數據被訪問後,引用計數增加,隊列重新排序
- 當需要淘汰數據時,將已經排序的列表最後的數據塊刪除
除了LRU,LFU,還有對於LFU算法的改進,比如Window-LFU,LFU-Aging等.
關於LRU的實現請參考我的博文
golang實現LRU
python實現LRU
哈希表和平衡二叉樹實現
import bisect
class LFUCache:
def __init__(self, capacity: int):
# 容量capacity和計時time
self.cap, self.time = capacity, 0
# 元素形式爲: (頻率,時間,鍵)(freq, time, key)
self.his = []
# 使用字典保存雙關鍵字-鍵值對形式爲key: [val, freq, time]
self.dic = {}
def get(self, key: int) -> int:
# key不存在,返回-1
if key not in self.dic:
return -1
# 更新該緩存的時間
self.time += 1
# 取出值,頻率和時間
val, freq, time = self.dic[key]
# 更新該緩存的使用頻率
self.dic[key][1] += 1 # 將頻率+1
self.dic[key][2] = self.time
# 找到history裏的記錄並移除原來緩存
self.his.pop(bisect.bisect_left(self.his, (freq, time, key)))
# 將更新後的記錄二分插入
bisect.insort(self.his, (freq+1, self.time, key))
return val
def put(self, key: int, value: int) -> None:
if not self.cap:
return
self.time += 1
# 查看哈希表中是否有對應鍵值
if key in self.dic:
# 取出頻率和時間
_, freq, time = self.dic[key]
self.dic[key][:] = value, freq + 1, self.time
# 找到history裏的記錄並移除
self.his.pop(bisect.bisect_left(self.his, (freq, time, key)))
# 將更新後的記錄二分插入
bisect.insort(self.his, (freq + 1, self.time, key))
else:
# 無該記錄
self.dic[key] = [value, 1, self.time]
# history容量是否已經滿了,滿了需要緩存淘汰
if len(self.his) == self.cap:
# 刪除最近最少使用緩存,因爲有序,移除history首個元素即對應的鍵值對
del self.dic[self.his.pop(0)[2]]
# 將新紀錄插入history
bisect.insort(self.his, (1, self.time, key))
if __name__ == '__main__':
capacity = 2
cache = LFUCache(capacity)
print(cache.put(2, 2))
print(cache.get(1))
print(cache.put(3, 3))
print(cache.get(2))
print(cache.get(3))
print(cache.put(4, 4))
print(cache.get(1))
print(cache.get(3))
print(cache.get(4))
"""
None
-1
None
2
3
None
-1
3
4
"""
雙哈希表實現
import collections
class Node:
def __init__(self, key, val, pre=None, nex=None, freq=0):
self.pre = pre
self.nex = nex
self.freq = freq # 當前節點使用頻率
self.val = val
self.key = key
# 插入節點
# self-> nex-> self.nex
def insert(self, nex):
nex.pre = self
nex.nex = self.nex
self.nex.pre = nex
self.nex = nex
# 創建雙向鏈表,包含值爲0的head,tail
def create_linked_list():
head = Node(0, 0)
tail = Node(0, 0)
head.nex = tail
tail.pre = head
return (head, tail)
class LFUCache:
def __init__(self, capacity: int):
self.capacity = capacity
self.size = 0 # 鍵值對總數
self.minFreq = 0 # 記錄最小的頻率,每次容量滿了,刪這個頻率的head.nex
self.freqMap = collections.defaultdict(
create_linked_list) # key是頻率,值是一條雙向鏈表的head, tail,最近操作的節點插入tail前面,則head.nex是最小使用頻率的節點,刪除時刪head.nex
self.keyMap = {} # 存儲鍵值對,值是node 類型
# 雙向鏈表中刪除指定節點
def delete(self, node):
if node.pre: # 不是第一個節點,就需要刪除,
node.pre.nex = node.nex # 前後前接起來
node.nex.pre = node.pre
if node.pre is self.freqMap[node.freq][0] and node.nex is self.freqMap[node.freq][
-1]: # 新的頻率中已存在這個節點,且只有這個節點,那就直接把這個新頻率刪掉,方便後面插入最新數據
self.freqMap.pop(node.freq)
return node.key
# 增加
def increase(self, node):
node.freq += 1 # 當前節點頻率+1
self.delete(node) # 舊頻率中,刪除此節點
self.freqMap[node.freq][-1].pre.insert(node) # 新頻率中,tail節點前插入當前節點
if node.freq == 1: # 出現頻率爲1的了,記錄一下,下次容量滿了先從這裏刪
self.minFreq = 1
elif self.minFreq == node.freq - 1: # 操作最小頻率的節點時,從舊頻率到新頻率時需要檢查下舊頻率,只有head,tail就不可能從這裏刪數據了,那就需要把minFreq更新爲新頻率,下次從這裏刪
head, tail = self.freqMap[node.freq - 1]
if head.nex is tail: # 這個頻率裏沒有實際節點,只有head,tail
self.minFreq = node.freq # 最小頻率更新爲節點當前頻率
def get(self, key: int) -> int:
if key in self.keyMap:
self.increase(self.keyMap[key])
return self.keyMap[key].val
return -1
def put(self, key: int, value: int) -> None:
if self.capacity != 0:
if key in self.keyMap: # 有,更新value
node = self.keyMap[key]
node.val = value
else:
node = Node(key, value) # 沒有,新建一個node
self.keyMap[key] = node
self.size += 1
if self.size > self.capacity: # 大於容量
self.size -= 1
deleted = self.delete(self.freqMap[self.minFreq][0].nex) # 刪除head.nex
self.keyMap.pop(deleted)
self.increase(node)