題目:LRU cache前端
Design and implement a data structure for Least Recently Used (LRU) cache. It should support the following operations: get and set. get(key) - Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1. set(key, value) - Set or insert the value if the key is not already present. When the cache reached its capacity, it should invalidate the least recently used item before inserting a new item.
LRU是一種應用在操做系統上的緩存替換策略,和咱們常見的FIFO算法同樣,都是用於操做系統中內存管理中的頁面替換,其全稱叫作Least Recently Used(近期最少使用算法),算法主要是根據數據的歷史訪問記錄來進行數據的淘汰,其核心思想是「若是數據最近被訪問過,那麼未來被訪問的概率也更高」。ios
LRU算法設計web
數據結構的選擇:由於涉及到數據元素的查找,刪除,替換,移動等操做,因此咱們選擇列表來進行數據的存儲,爲了考慮時間複雜度,咱們分析一下,單鏈表插入刪除操做的時間複雜度爲O(n),雙鏈表爲O(1),因此,首選確定是雙鏈表,另外,元素的查找操做,map的查找效率爲O(lgn),首選應該是map,但還有一個hashmap,可以達到O(1)的查找效率,咱們後面再編程的時候都試一下這幾種方法,看看其能不能經過編譯,經過了時間又是多少?算法
爲了可以比較形象的瞭解LRU的執行過程,咱們舉一個例子,以下:編程
假定現有一進程的頁面訪問序列爲:緩存
4,7,0,7,1,0,1,2,1,2,6數據結構
緩存容量爲5,則隨着進程的訪問,緩存棧中頁面號的變化狀況以下圖所示。在訪問頁面6時發生了缺頁,此時頁面4是最近最久未被訪問的頁,應將它置換出去。spa
在算法實現時,咱們能夠把最近最久沒有使用的數據放在鏈表的最後,當緩存空間滿時(即發生缺頁),直接將最後一個數據淘汰便可,同時,若是一個數據發生命中,或者新來一個數據,咱們都將該數據移到鏈表的頭部,這樣就能保證在鏈表頭部的數據都是最近訪問過的,而鏈表後面的數據就是最近最久沒有訪問過的。以下所示:操作系統
代碼實現,爲了驗證上面所提出數據結構是否能經過LeetCode的編譯,咱們都實現一遍,下面是single list+map的實現,時間複雜度爲O(n)+O(lgn),開始我還覺得經過不了,最後仍是經過了,耗時大約900ms。.net
/************************************************************************/ /* 單鏈表版本 /************************************************************************/ struct Node { int m_nKey; int m_nValue; Node* m_pNext; }; class LRUCache { public: LRUCache(int capacity) { m_nSize = capacity; m_nCount = 0; m_lruList = NULL; } int get(int key) { if (NULL == m_lruList) return -1; map<int, Node *>::iterator it = m_lruMap.find(key); if (it == m_lruMap.end()) //沒有找到 return -1; else { Node *p = it->second; //把節點移到鏈表的開頭 pushFront(p); } return m_lruList->m_nValue; } void set(int key, int value) { if (NULL == m_lruList) { m_lruList = new Node(); m_lruList->m_nKey = key; m_lruList->m_nValue = value; m_lruList->m_pNext = NULL; m_nCount ++; m_lruMap[key] = m_lruList; } else { map<int, Node *>::iterator it = m_lruMap.find(key); if (it == m_lruMap.end()){ //沒有命中,將鏈表的最後一個節點刪除 if (m_nSize == m_nCount) { //cache已滿 Node *pHead = m_lruList; Node *pTemp = pHead; while(pHead->m_pNext != NULL) { pTemp = pHead; pHead = pHead->m_pNext; } m_lruMap.erase(pHead->m_nKey); m_nCount --; if (pHead == pTemp) //只有一個節點 pHead = NULL; else pTemp->m_pNext = NULL; } Node *p = new Node(); //插入新的節點於頭部 p->m_nKey = key; p->m_nValue = value; p->m_pNext = m_lruList; m_lruList = p; m_lruMap[key] = m_lruList; m_nCount ++; } else { //命中,則將該命中的節點移至鏈表頭部 Node *pCur = it->second; pCur->m_nValue = value; pushFront(pCur); } } } void pushFront(Node *pCur) { //把節點移動到鏈表頭部,時間複雜度O(n) if (NULL == pCur) return; if (m_nCount == 1 || pCur == m_lruList) return; Node *pHead = m_lruList; while (pHead->m_pNext != pCur) pHead = pHead->m_pNext; pHead->m_pNext = pCur->m_pNext; pCur->m_pNext = m_lruList; m_lruList = pCur; } void printCache() { Node *p = m_lruList; while (p) { cout << p->m_nKey << ":" << p->m_nValue << " "; p = p->m_pNext; } } private: int m_nSize; int m_nCount; map<int, Node *> m_lruMap; Node* m_lruList; };
下面是double list+map版本,時間複雜度爲O(1)+O(lgn),耗時大約300s
1 /************************************************************************/ 2 /* 雙鏈表版本 3 /************************************************************************/ 4 struct Node { 5 int m_nKey; 6 int m_nValue; 7 Node* m_pNext; 8 Node* m_pPre; 9 }; 10 11 class LRUCache { 12 public: 13 LRUCache(int capacity) { 14 m_nSize = capacity; 15 m_nCount = 0; 16 m_lruListHead = NULL; 17 m_lruListTail = NULL; 18 } 19 20 int get(int key) { 21 if (NULL == m_lruListHead) 22 return -1; 23 map<int, Node *>::iterator it = m_lruMap.find(key); 24 if (it == m_lruMap.end()) //沒有找到 25 return -1; 26 else { 27 Node *p = it->second; 28 //把節點移到鏈表的開頭 29 pushFront(p); 30 } 31 return m_lruListHead->m_nValue; 32 } 33 34 void set(int key, int value) { 35 if (NULL == m_lruListHead) { 36 m_lruListHead = new Node(); 37 m_lruListHead->m_nKey = key; 38 m_lruListHead->m_nValue = value; 39 m_lruListHead->m_pNext = NULL; 40 m_lruListHead->m_pPre = NULL; 41 m_lruListTail = m_lruListHead; 42 m_nCount ++; 43 m_lruMap[key] = m_lruListHead; 44 } 45 else { 46 map<int, Node *>::iterator it = m_lruMap.find(key); 47 if (it == m_lruMap.end()){ //沒有命中,將鏈表的最後一個節點刪除 48 if (m_nSize == m_nCount) { //cache已滿 49 if (m_lruListHead == m_lruListTail) {//只有一個節點 50 m_lruMap.erase(m_lruListHead->m_nKey); 51 m_lruListHead->m_nKey = key; 52 m_lruListHead->m_nValue = value; 53 m_lruMap[key] = m_lruListHead; 54 } 55 else { 56 Node *p = m_lruListTail; 57 m_lruListTail->m_pPre->m_pNext = NULL; 58 m_lruListTail = m_lruListTail->m_pPre; 59 m_lruMap.erase(p->m_nKey); 60 61 p->m_nKey = key; 62 p->m_nValue = value; 63 p->m_pNext = m_lruListHead; 64 p->m_pPre = NULL; 65 m_lruListHead->m_pPre = p; 66 m_lruListHead = p; 67 m_lruMap[key] = m_lruListHead; 68 } 69 } 70 else { 71 Node *p = new Node(); //插入新的節點於頭部 72 p->m_nKey = key; 73 p->m_nValue = value; 74 p->m_pNext = m_lruListHead; 75 p->m_pPre = NULL; 76 m_lruListHead->m_pPre = p; 77 m_lruListHead = p; 78 m_lruMap[key] = m_lruListHead; 79 m_nCount ++; 80 } 81 } 82 else { //命中,則將該命中的節點移至鏈表頭部 83 Node *pCur = it->second; 84 pCur->m_nValue = value; 85 pushFront(pCur); 86 } 87 } 88 } 89 90 void pushFront(Node *pCur) { //把節點移動到鏈表頭部,時間複雜度O(1) 91 if (NULL == pCur) 92 return; 93 if (m_nCount == 1 || pCur == m_lruListHead) 94 return; 95 if (pCur == m_lruListTail) { //假如是尾節點 96 pCur->m_pPre->m_pNext = NULL; 97 pCur->m_pNext = m_lruListHead; 98 m_lruListTail = pCur->m_pPre; 99 m_lruListHead->m_pPre = pCur; 100 m_lruListHead = pCur; 101 } 102 else { 103 pCur->m_pPre->m_pNext = pCur->m_pNext; 104 pCur->m_pNext->m_pPre = pCur->m_pPre; 105 106 pCur->m_pNext = m_lruListHead; 107 m_lruListHead->m_pPre = pCur; 108 m_lruListHead = pCur; 109 } 110 } 111 112 void printCache() { 113 Node *p = m_lruListHead; 114 while (p) { 115 cout << p->m_nKey << ":" << p->m_nValue << " "; 116 p = p->m_pNext; 117 } 118 } 119 120 private: 121 int m_nSize; 122 int m_nCount; 123 map<int, Node *> m_lruMap; 124 Node* m_lruListHead; 125 Node* m_lruListTail; 126 };
下面是hashmap+list版本,若是是C++,list和hashmap都是STL自帶的功能實現,因此,咱們直接應用STL庫,代碼量大大減小,時間複雜度爲O(1).^-^代碼參考:dancingrain
#include <iostream> #include <hash_map> #include <list> #include <utility> using namespace std; using namespace stdext; class LRUCache{ public: LRUCache(int capacity) { m_capacity = capacity ; } int get(int key) { int retValue = -1 ; hash_map<int, list<pair<int, int> > :: iterator> ::iterator it = cachesMap.find(key) ; //若是在Cashe中,將記錄移動到鏈表的最前端 if (it != cachesMap.end()) { retValue = it ->second->second ; //移動到最前端 list<pair<int, int> > :: iterator ptrPair = it -> second ; pair<int, int> tmpPair = *ptrPair ; caches.erase(ptrPair) ; caches.push_front(tmpPair) ; //修改map中的值 cachesMap[key] = caches.begin() ; } return retValue ; } void set(int key, int value) { hash_map<int, list<pair<int, int> > :: iterator> ::iterator it = cachesMap.find(key) ; if (it != cachesMap.end()) //已經存在其中 { list<pair<int, int> > :: iterator ptrPait = it ->second ; ptrPait->second = value ; //移動到最前面 pair<int , int > tmpPair = *ptrPait ; caches.erase(ptrPait) ; caches.push_front(tmpPair) ; //更新map cachesMap[key] = caches.begin() ; } else //不存在其中 { pair<int , int > tmpPair = make_pair(key, value) ; if (m_capacity == caches.size()) //已經滿 { int delKey = caches.back().first ; caches.pop_back() ; //刪除最後一個 //刪除在map中的相應項 hash_map<int, list<pair<int, int> > :: iterator> ::iterator delIt = cachesMap.find(delKey) ; cachesMap.erase(delIt) ; } caches.push_front(tmpPair) ; cachesMap[key] = caches.begin() ; //更新map } } private: int m_capacity ; //cashe的大小 list<pair<int, int> > caches ; //用一個雙鏈表存儲cashe的內容 hash_map< int, list<pair<int, int> > :: iterator> cachesMap ; //使用map加快查找的速度 };