LRU与LFU的原理与实现

LRU与LFU的原理与实现python

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
    

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值