zoukankan      html  css  js  c++  java
  • [leetcode]LRU Cache @ Python

    原题地址:http://oj.leetcode.com/problems/lru-cache/

    题意:设计LRU Cache

    参考文献:http://blog.csdn.net/hexinuaa/article/details/6630384 这篇博文总结的很到位。

           https://github.com/Linzertorte/LeetCode-in-Python/blob/master/LRUCache.py 代码参考的github人写的,思路非常清晰,写的也很好。

    Cache简介:

    Cache(高速缓存), 一个在计算机中几乎随时接触的概念。CPU中Cache能极大提高存取数据和指令的时间,让整个存储器(Cache+内存)既有Cache的高速度,又能有内存的大容量;操作系统中的内存page中使用的Cache能使得频繁读取的内存磁盘文件较少的被置换出内存,从而提高访问速度;数据库中数据查询也用到Cache来提高效率;即便是Powerbuilder的DataWindow数据处理也用到了Cache的类似设计。Cache的算法设计常见的有FIFO(first in first out)和LRU(least recently used)。根据题目的要求,显然是要设计一个LRU的Cache。

    解题思路:

    Cache中的存储空间往往是有限的,当Cache中的存储块被用完,而需要把新的数据Load进Cache的时候,我们就需要设计一种良好的算法来完成数据块的替换。LRU的思想是基于“最近用到的数据被重用的概率比较早用到的大的多”这个设计规则来实现的。

    为了能够快速删除最久没有访问的数据项和插入最新的数据项,我们双向链表连接Cache中的数据项,并且保证链表维持数据项从最近访问到最旧访问的顺序。每次数据项被查询到时,都将此数据项移动到链表头部(O(1)的时间复杂度)。这样,在进行过多次查找操作后,最近被使用过的内容就向链表的头移动,而没有被使用的内容就向链表的后面移动。当需要替换时,链表最后的位置就是最近最少被使用的数据项,我们只需要将最新的数据项放在链表头部,当Cache满时,淘汰链表最后的位置就是了。

      注: 对于双向链表的使用,基于两个考虑。首先是Cache中块的命中可能是随机的,和Load进来的顺序无关。其次,双向链表插入、删除很快,可以灵活的调整相互间的次序,时间复杂度为O(1)。

        查找一个链表中元素的时间复杂度是O(n),每次命中的时候,我们就需要花费O(n)的时间来进行查找,如果不添加其他的数据结构,这个就是我们能实现的最高效率了。目前看来,整个算法的瓶颈就是在查找这里了,怎么样才能提高查找的效率呢?Hash表,对,就是它,数据结构中之所以有它,就是因为它的查找时间复杂度是O(1)。

    梳理一下思路:对于Cache的每个数据块,我们设计一个数据结构来储存Cache块的内容,并实现一个双向链表,其中属性next和prev是双向链表的两个指针,key用于存储对象的键值,value用于存储cache块对象本身。

    Cache的接口:

    查询:

    • 根据键值查询hashmap,若命中,则返回节点key值对应的value,否则返回-1。
    • 从双向链表中删除命中的节点,将其重新插入到表头。
    • 所有操作的复杂度均为O(1)。

    插入:

    • 将新的节点关联到Hashmap
    • 如果Cache满了,删除双向链表的尾节点,同时删除Hashmap对应的记录
    • 将新的节点插入到双向链表中头部

    更新:

    • 和查询相似

    删除:

    • 从双向链表和Hashmap中同时删除对应的记录。

    双向链表示意图:

    代码:

    class Node:                          #双向链表中节点的定义
        def __init__(self,k,x):
            self.key=k
            self.val=x
            self.prev=None
            self.next=None
    
    class DoubleLinkedList:                   #双向链表是一个表头,head指向第一个节点,tail指向最后一个节点
        def __init__(self):
            self.tail=None
            self.head=None
            
        def isEmpty():                      #如果self.tail==None,那么说明双向链表为空
            return not self.tail
        def removeLast(self):                  #删除tail指向的节点
            self.remove(self.tail)
            
        def remove(self,node):                  #具体双向链表节点删除操作的实现
            if self.head==self.tail:
                self.head,self.tail=None,None
                return
            if node == self.head:
                node.next.prev=None
                self.head=node.next
                return
            if node ==self.tail:
                node.prev.next=None
                self.tail=node.prev
                return
            node.prev.next=node.next
            node.next.prev=node.prev
            
        def addFirst(self,node):                  #在双向链表的第一个节点前面再插入一个节点  
            if not self.head:
                self.head=self.tail=node
                node.prev=node.next=None
                return
            node.next=self.head
            self.head.prev=node
            self.head=node
            node.prev=None
    
    class LRUCache:
    
        # @param capacity, an integer
        def __init__(self, capacity):              #初始化LRU Cache
            self.capacity=capacity                #LRU Cache的容量大小             
            self.size=0                      #LRU Cache目前占用的容量
            self.P=dict()                     #dict为文章中提到的hashmap,加快搜索速度,{key:对应节点的地址}
            self.cache=DoubleLinkedList()
        # @return an integer
        def get(self, key):                    #查询操作
            if (key in self.P) and self.P[key]:        #如果key在字典中
                self.cache.remove(self.P[key])         #将key对应的指针指向的节点删除
                self.cache.addFirst(self.P[key])        #然后将这个节点添加到双向链表头部
                return self.P[key].val              #并返回节点的value
            else: return -1
    
        # @param key, an integer
        # @param value, an integer
        # @return nothing
        def set(self, key, value):                #设置key对应的节点的值为给定的value值
            if key in self.P:                   #如果key在字典中
                self.cache.remove(self.P[key])          #先删掉key对应的节点
                self.cache.addFirst(self.P[key])        #然后将这个节点插入到表的头部
                self.P[key].val=value               #将这个节点的值val改写为value
            else:                          #如果key不在字典中
                node=Node(key,value)               #新建一个Node节点,val值为value
                self.P[key]=node                  #将key和node的对应关系添加到字典中
                self.cache.addFirst(node)            #将这个节点添加到链表表头
                self.size +=1                   #容量加1
                if self.size > self.capacity:           #如果链表的大小超过了缓存的大小,删掉最末尾的节点,
                    self.size -=1                 #并同时删除最末尾节点key值和末节点在字典中的对应关系
                    del self.P[self.cache.tail.key]
                    self.cache.removeLast()
    class LRUCache:
        # @param capacity, an integer
        def __init__(self, capacity):
            LRUCache.capacity = capacity
            LRUCache.length = 0
            LRUCache.dict = collections.OrderedDict()
    
        # @return an integer        
        def get(self, key):
            try:
                value = LRUCache.dict[key]
                del LRUCache.dict[key]
                LRUCache.dict[key] = value
                return value
            except:
                return -1
                
        # @param key, an integer
        # @param value, an integer
        # @return nothing        
        def set(self, key, value):
            try:
                del LRUCache.dict[key]
                LRUCache.dict[key] = value
            except:
                if LRUCache.length == LRUCache.capacity:
                    LRUCache.dict.popitem(last = False)
                    LRUCache.length -= 1
                LRUCache.dict[key] = value
                LRUCache.length +=1
  • 相关阅读:
    0008_Python变量
    shiro Filter过滤器管理197
    oracle 将一个数据库(A)的表导入到另一个数据库197
    top命令使用197
    SpringBoot下载Excel文件,解决文件损坏问题197
    java元注解197
    Content-Type
    centos7 下修改网络配置
    mint 20 install NVIDIA driver for 3080 via run
    使用numpy rot90操作image后,opencv cv2.rectangle 报错
  • 原文地址:https://www.cnblogs.com/zuoyuan/p/3701572.html
Copyright © 2011-2022 走看看