zoukankan      html  css  js  c++  java
  • 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(近期最少使用算法),算法主要是根据数据的历史访问记录来进行数据的淘汰,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。

    LRU算法设计

    数据结构的选择:因为涉及到数据元素的查找,删除,替换,移动等操作,所以我们选择列表来进行数据的存储,为了考虑时间复杂度,我们分析一下,单链表插入删除操作的时间复杂度为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是最近最久未被访问的页,应将它置换出去。

    在算法实现时,我们可以把最近最久没有使用的数据放在链表的最后,当缓存空间满时(即发生缺页),直接将最后一个数据淘汰即可,同时,如果一个数据发生命中,或者新来一个数据,我们都将该数据移到链表的头部,这样就能保证在链表头部的数据都是最近访问过的,而链表后面的数据就是最近最久没有访问过的。如下所示:

    代码实现,为了验证上面所提出数据结构是否能通过LeetCode的编译,我们都实现一遍,下面是single list+map的实现,时间复杂度为O(n)+O(lgn),开始我还以为通过不了,最后还是通过了,耗时大约900ms。

    /************************************************************************/
    /* 单链表版本                                                                    
    /************************************************************************/
    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加快查找的速度
    };
    
  • 相关阅读:
    安卓学习26(ListView的焦点问题)
    ACL Contest1 B- Sum is Multiple 数论,EXGCD
    P4137 Rmq Problem / mex 可持久化线段树求区间MEX
    [可持久化权值线段树] [模板] [数组版本]
    CodeForces383C Propagating tree 思维 线段树 DFS序
    CodeForces 558E A Simple Task 线段树 桶排思想
    P1471 方差 线段树维护区间方差
    Leetcode1521 找到最接近目标的函数值 位运算
    CodeForces-739C Alyona and towers 线段树经典套路之维护左中右
    GYM-101194F Mr. Panda and Fantastic Beasts 后缀数组套路
  • 原文地址:https://www.cnblogs.com/bakari/p/4016318.html
Copyright © 2011-2022 走看看