LeetCode:146_LRU cache | LRU緩存設計 | Hard

題目: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加快查找的速度
};
相關文章
相關標籤/搜索