• 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值,
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    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       
    
    • 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

    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,
    				如果频率哈希表中不存在对应的链表需要创建
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    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
        
    
    
    • 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
  • 相关阅读:
    沙丁鱼优化算法(Sardine optimization algorithm,SOA)求解23个函数MATLAB
    Spring 6.X IoC 容器
    Canal
    聊聊Redis sentinel 机制
    流程图 and/or/xor 讲解
    解决Mac下窗口无法最大化的问题 - 只有最小化和全屏怎么够
    组合数的计算
    彻底搞懂Swagger&Knife4j使用方法
    LegalQA 数据集 样例数据
    尚医通-微信支付
  • 原文地址:https://blog.csdn.net/weixin_41744192/article/details/125447906