Leetcode经典系列——LRU最近最少使用机制

目录

LeetCode题目:

用两个栈实现,会超时:

哈希+链表改进分析:


简写:Least Recently Used,是一种常用的页面置换算法是一种缓存淘汰策略

计算机的缓存容量有限,如果缓存满了就要删除一些内容,给新内容腾位置。但问题是,删除哪些内容呢?我们肯定希望删掉哪些没什么用的缓存,而把有用的数据继续留在缓存里,方便之后继续使用。那么,什么样的数据,我们判定为「有用的」的数据呢?

LRU 缓存淘汰算法就是一种常用策略。LRU 的全称是 Least Recently Used,也就是说我们认为最近使用过的数据应该是是「有用的」,很久都没用过的数据应该是无用的,内存满了就优先删那些很久没用过的数据。

最近最少使用算法(LRU)是大部分操作系统为最大化页面命中率而广泛采用的一种页面置换算法。该算法的思路是,发生缺页中断时,选择未使用时间最长的页面置换出去。 [1]  从程序运行的原理来看,最近最少使用算法是比较接近理想的一种页面置换算法,这种算法既充分利用了内存中页面调用的历史信息,又正确反映了程序的局部问题。利用

Leetcode经典系列——LRU最近最少使用机制_第1张图片

LRU 算法对上例进行页面置换的结果如图1所示。当进程第一次对页面 2 进行访问时,由于页面 7 是最近最久未被访问的,故将它置换出去。当进程第一次对页面 3进行访问时,第 1 页成为最近最久未使用的页,将它换出。由图1可以看出,前 5 个时间的图像与最佳置换算法时的相同,但这并非是必然的结果。因为,最佳置换算法是从“向后看”的观点出发的,即它是依据以后各页的使用情况;而 LRU 算法则是“向前看”的,即根据各页以前的使用情况来判断,而页面过去和未来的走向之间并无必然的联系。

LeetCode题目:

Leetcode经典系列——LRU最近最少使用机制_第2张图片

用两个栈实现,会超时:

  • 每一次get之后,如果找到,记得将找到的置顶
  • 每一个put之后,如果有匹配的key,记得将这一对key-value置顶
  • put还需要考虑如果超出最大限制,需要删除最末一个
  • get需要考虑如果得到,需要将得到的置顶
class LRUCache {
private:
    int maxSize;
    stack> sta;
public:
    LRUCache(int capacity) {
        maxSize = capacity;
    }
    
    int get(int key) {
        if(sta.empty())
            return -1;
        stack> temp;
        bool flag = false;
        int ans;
        pair p;
        while(!sta.empty()){
            pair a = sta.top();
            temp.push(a);
            sta.pop();
            if(a.first==key){
                flag = true;
                ans = a.second;
                break;
            }
        }
        if(flag){//将刚才用的放到最顶端
            p = temp.top();
            temp.pop();
        }
        while(!temp.empty()){
            pair a = temp.top();
            sta.push(a);
            temp.pop();
        }
        if(flag){
            sta.push(p);
            return ans;
        } 
        return -1;
    }
    
    void put(int key, int value) {
        bool flag = false;
        stack> temp;  
        pair p;
        while(!sta.empty()){
            pair a = sta.top();
            if(a.first==key){//密钥已存在,更新密钥并置顶
                a.second=value;
                flag = true;
                p = a;
                break;  
            }
            temp.push(a);
            sta.pop();
        }
        if(flag){//如果是更新密钥,则将其置顶
            sta.pop();
            while(!temp.empty()){
                pair a = temp.top();
                sta.push(a);
                temp.pop();
            }
            sta.push(p);
            return;
        }
        if(temp.size()>=maxSize)//如果大小超过则删去最后一个元素
            temp.pop();
        while(!temp.empty()){
            pair a = temp.top();
            sta.push(a);
            temp.pop();
        }
        sta.push(make_pair(key,value));
    }
};

/**
 * Your LRUCache object will be instantiated and called as such:
 * LRUCache* obj = new LRUCache(capacity);
 * int param_1 = obj->get(key);
 * obj->put(key,value);
 */

哈希+链表改进分析:

要让 put 和 get 方法的时间复杂度为 O(1),我们可以总结出 cache 这个数据结构必要的条件:查找快,插入快,删除快,有顺序之分。

因为显然 cache 必须有顺序之分,以区分最近使用的和久未使用的数据;而且我们要在 cache 中查找键是否已存在;如果容量满了要删除最后一个数据;每次访问还要把数据插入到队头。

那么,什么数据结构同时符合上述条件呢?哈希表查找快,但是数据无固定顺序;链表有顺序之分,插入删除快,但是查找慢。所以结合一下,形成一种新的数据结构:哈希链表。

class LRUCache {
private:
    int maxSize;
    list> cache;
    unordered_map>::iterator> map;//map底层是红黑树,不满足O(1)
public:
    LRUCache(int capacity) {
        this->maxSize = capacity;
    }
    
    int get(int key) {
        auto it = map.find(key);
        if(it == map.end())//没有找到
            return -1;
        //找到了对应的key,则添加到链表头部
        pair p = *map[key];
        cache.erase(map[key]);
        cache.push_front(p);
        //在哈希表中更新位置
        map[key] = cache.begin();
        return p.second;
    }
    
    void put(int key, int value) {
        auto it = map.find(key);
        if(it==map.end()){//key不存在
            if(cache.size()==maxSize){//已经满了
                //对应删除哈希表和链表
                auto p = cache.back();
                int lastKey = p.first;
                map.erase(lastKey);
                cache.pop_back();
            }
            //添加
            cache.push_front(make_pair(key,value));
            map[key] = cache.begin();//将添加的指向链表头部
        }else{//key存在
            //直接将key的位置更换到对头即可
            cache.erase(map[key]);
            cache.push_front(make_pair(key,value));
            map[key] = cache.begin();
        }
    }
};

/**
 * Your LRUCache object will be instantiated and called as such:
 * LRUCache* obj = new LRUCache(capacity);
 * int param_1 = obj->get(key);
 * obj->put(key,value);
 */

这方法牛逼,之后还是要多刷两遍啊!

你可能感兴趣的:(LeetCode经典,LeetCode,算法—STL与数据结构)