LRU
LRU(Least Recently Used) 最近最少使用算法,它是根据时间维度来选择将要淘汰的元素,即删除掉最长时间没被访问的元素,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。
一般LRUCache的 get 和 put 必须以 O(1) 的平均时间复杂度运行,必要的条件:查找快,插⼊快,删除快,有顺序 。
首先为了保证数据的读写效率,这就可以采用key-value的哈希结构来帮助我们实现,为了保证数据的顺序可以借助链表来实现,同时为了提高链表结点的插删效率一般使用双向链表,双向链表在一般情况下的插入、删除等操作都要比单链表简单、高效。
LRUCache 类主要特点如下:
capacity以 正整数 作为容量 capacity 初始化 LRU 缓存
get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
put(int key, int value) 如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ,则应该 逐出 最久未使用的关键字。函数 get 和 put 必须以 O(1) 的平均时间复杂度运行。
#put的流程
1.key已经存在:
1.在哈希中查询所在的结点,
2.改变结点原有的value,
3.更新结点到头部,
2.key不存在:
1.检查容量是否达到capacity,超出就删除链表尾部结点和对应的哈希,
2.创建新节点存储key-value,
3.将结点插入到头部
#get操作的流程
1.key不存在,直接返回-1
2.key存在:
1.在哈希中查询所在的节点
2.更新结点到链表头部
3.返回节点的value值,
class lrunode():
def __init__(self,key=None,val=None,pre=None,next=None):
self.key = key
self.val = val
self.pre = pre
self.next = next
class LRUCache:
def __init__(self, capacity: int):
self.head = lrunode()
self.tail = lrunode()
self.head.next = self.tail
self.tail.pre = self.head
self.cache = dict()
self.cache_szie = 0
self.capacity = capacity
def move_head(self,node_address):
if self.head.next == node_address:return
node_address.pre.next = node_address.next
node_address.next.pre = node_address.pre
node_address.next = self.head.next
node_address.pre = self.head
self.head.next.pre = node_address
self.head.next = node_address
return
def add_head(self,node_address):
node_address.next = self.head.next
node_address.pre = self.head
self.head.next.pre = node_address
self.head.next = node_address
return
def del_tail(self):
remove_node = self.tail.pre
remove_node.pre.next = self.tail
self.tail.pre = remove_node.pre
return remove_node
def get(self, key):
if key not in self.cache:return -1
node_address = self.cache[key]
self.move_head(node_address)
return node_address.val
def put(self, key, value):
if key in self.cache:
node_address = self.cache[key]
node_address.val = value
self.move_head(node_address)
else:
if self.cache_szie > self.capacity:
remove_node = self.del_tail()
self.cache.pop(remove_node.key)
self.cache_szie -= 1
del remove_node
node_address = lrunode(key,value)
self.cache[key] = node_address
self.add_head(node_address)
self.cache_szie += 1
LFU
参考:leetcode题解
LFU(Least Frequently Used) 最近最不常用算法,它是根据频率维度来选择将要淘汰的元素,即删除访问频率最低的元素。如果两个元素的访问频率相同,则淘汰最久没被访问的元素。
也就是说LFU淘汰的时候会选择两个维度,先比较频率,选择访问频率最小的元素;如果频率相同,则按时间维度淘汰掉最久远的那个元素。
如何实现呢,首先为了保证时间维度,哈希加双向链表的结构肯定是也要采用的,对于频率,可以采用另一个哈希表来存储,为了将这个频率哈希表与链表建立联系,将同一频率的节点放在同一条双向链表上,结点的顺序依旧可以像lru那样表示时间维度,再将链表作为频率哈希表的value值。结构如下:
get操作的具体逻辑大致是这样:
如果key不存在则返回-1
如果key存在,则返回对应的value,同时:
将元素的访问频率+1
将元素从访问频率i的链表中移除,放到频率i+1的链表中
如果频率i的链表为空,则从频率哈希表中移除这个链表
put操作
1.如果key已经存在,
修改对应结点的value,
并将访问频率+1
将元素从访问频率i的链表中移除,放到频率i+1的链表中
经过上一步,如果频率i的链表变为空,则从频率哈希表中移除这个链表
2.如果key不存在
判断缓存是否超过最大容量,
超出:
则先删除访问频率最低链表的最后一个元素,
插入新元素,新元素的访问频率为1,
如果频率哈希表中不存在对应的链表需要创建
未超出:
直接插入新元素,新元素的访问频率为1,
如果频率哈希表中不存在对应的链表需要创建
class lfunode():
def __init__(self,key=None,val=None,freq=None,pre=None,next=None):
self.key = key
self.val = val
self.freq = freq
self.pre = pre
self.next = next
class link_list():
def __init__(self):
self.head = lfunode()
self.tail = lfunode()
self.head.next = self.tail
self.tail.pre = self.head
def add_head(self,node_address):
node_address.next = self.head.next
node_address.pre = self.head
self.head.next.pre = node_address
self.head.next = node_address
return
def del_linklist_node(self,node_address):
node_address.pre.next = node_address.next
node_address.next.pre = node_address.pre
#del node_address
return
def get_last(self):
return self.tail.pre
def is_empty(self):
if self.head.next == self.tail:return True
return False
class LFUCache:
def __init__(self, capacity: int):
self.key_cache = dict()
self.freq_cache = dict()
self.min_freq = 0
self.capacity = capacity
def set_linklist(self,node_address):
if node_address.freq not in self.freq_cache:
self.freq_cache[node_address.freq] = link_list()
linklist = self.freq_cache[node_address.freq]
linklist.add_head(node_address)
return
def del_node(self,node_address):
freq = node_address.freq
linklist = self.freq_cache[freq]
linklist.del_linklist_node(node_address)
if linklist.is_empty():
del self.freq_cache[freq]
return
def update_freq_seq(self, node_address, is_new_node=False):
if is_new_node:
self.min_freq = 1
self.set_linklist(node_address)
else:
self.del_node(node_address)
node_address.freq += 1
self.set_linklist(node_address)
if self.min_freq not in self.freq_cache:
self.min_freq += 1
return
def del_min_freq(self):
linklist = self.freq_cache[self.min_freq]
node_address = linklist.get_last()
linklist.del_linklist_node(node_address)
del self.key_cache[node_address.key]
if linklist.is_empty():
del self.freq_cache[node_address.freq]
return
def get(self, key: int) -> int:
if key not in self.key_cache:return -1
node_address = self.key_cache[key]
self.update_freq_seq(node_address)
return node_address.val
def put(self, key: int, value: int) -> None:
if key in self.key_cache:
node_address = self.key_cache[key]
node_address.val = value
self.update_freq_seq(node_address)
else:
if self.capacity==0:
return
if len(self.key_cache) == self.capacity:
self.del_min_freq()
new_node = lfunode(key,value,1)
self.update_freq_seq(new_node,True)
self.key_cache[key] = new_node