原題地址: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