[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

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM