LRU/LeetCode 146

5 篇文章 0 订阅
1 篇文章 0 订阅

题目链接:https://leetcode-cn.com/problems/lru-cache/
在这里插入图片描述

  • 快速查找到这个元素不难想到:哈希表。
  • 关键是如何再设计一个数据结构去O(1)删除和插入,这样子的话很明显必须是个线性结构了(链表、栈、队列(这俩个只能在头尾)等),但是发现我们可能需要在线性结构中间删除某个元素(当key相同的时候,我们需要更新value),假如不会出现相同值的情况也只能用deque(因为头尾都需要可以进行操作),所以我们在这里选择链表。但这里,我一开始想用单调队列或者单调栈,但是好像维护不了,因为虽然出现次数的多少确实可以和单调扯上关系,但是好像无法在O(1)内维护
  • 那么就很好办了,哈希映射链表中的位置(迭代器),因为我们只能知道当前元素的位置,而在链表中删除我们还需要知道前一个结点的信息,所以这里用双链表
  • 还有一个问题:双链表里存什么信息呢?存key和value,不能只存个value,因为超出内存要删除元素时,我们必须通过链表尾的key删除哈希表里的那个元素
  • 这道题对我的启发,对不经常用这个状态该如何维护:1.统计get的次数(但是get次数都是0,仍然再put的话,就还要维护put的顺序) 2.可以像链表这样按顺序头部添加,末尾删除,因为既然头部是才添加的(put/get),那么尾部就是LRU的那个元素。
class LRUCache {
public:
    int cap;
    list<pair<int,int>> cache;
    unordered_map<int,list<pair<int,int>>::iterator> hash_map;

    LRUCache(int capacity) {
        this->cap = capacity;
    }
    
    int get(int key) {
        //快速查找 -> 哈希
        if(!hash_map.count(key)) return -1;

        pair<int,int> cur = *hash_map[key];
        cache.push_front(cur);
        cache.erase(hash_map[key]);
        hash_map[key] = cache.begin();
        return cur.second;
    }
    
    void put(int key, int value) {
        //先看是否存在
        if(hash_map.count(key)){
            pair<int,int> cur = *hash_map[key];
            cur.second = value;
            cache.erase(hash_map[key]);
            cache.push_front(cur);
            hash_map[key] = cache.begin(); //调整下次序
        }else{
            //size大了删除尾巴上的最少used element
            if(cache.size() >= cap){
                int k = cache.back().first; //find the key
       
                hash_map.erase(k);
                cache.pop_back();
            }
            //put new element
            pair<int,int> cur{key,value};
            cache.push_front(cur);
            hash_map[key] = cache.begin();
        }
    }
};

  • LRU是一种页面置换算法
    在地址映射过程中,若在页面中发现所要访问的页面不在内存中,则产生缺页中断。当发生缺页中断时,如果操作系统内存中没有空闲页面,则操作系统必须在内存选择一个页面将其移出内存,以便为即将调入的页面让出空间。而用来选择淘汰哪一页的规则叫做页面置换算法

  • OPT:无法实现

  • FIFO:这种算法的实质是,总是选择在主存中停留时间最长(即最老)的一页置换,即先进入内存的页,先退出内存。理由是:最早调入内存的页,其不再被使用的可能性比刚调入内存的可能性大。建立一个FIFO队列,收容所有在内存中的页。被置换页面总是在队列头上进行。当一个页面被放入内存时,就把它插在队尾上。
    这种算法只是在按线性顺序访问地址空间时才是理想的,否则效率不高。因为那些常被访问的页,往往在主存中也停留得最久,结果它们因变“老”而不得不被置换出去。(也就是访问了某个页面,但是它在队列里的位置并不会改变,它是以进入内存的时间长短作为置换标准的

  • LRU:LRU算法是与每个页面最后使用的时间有关的。当必须置换一个页面时,LRU算法选择过去一段时间里最久未被使用的页面。
    1.计数器。最简单的情况是使每个页表项对应一个使用时间字段,并给CPU增加一个逻辑时钟或计数器。每次存储访问,该时钟都加1。每当访问一个页面时,时钟寄存器的内容就被复制到相应页表项的使用时间字段中。这样我们就可以始终保留着每个页面最后访问的“时间”。在置换页面时,选择该时间值最小的页面。这样做,不仅要查页表,而且当页表改变时(因CPU调度)要维护这个页表中的时间,还要考虑到时钟值溢出的问题。(这个就对应了我之前说的记get次数的方法)
    2.栈(但是要用双链表实现)。用一个栈保留页号。每当访问一个页面时,就把它从栈中取出放在栈顶上。这样一来,栈顶总是放有目前使用最多的页,而栈底放着目前最少使用的页。由于要从栈的中间移走一项,所以要用具有头尾指针的双向链连起来。在最坏的情况下,移走一页并把它放在栈顶上需要改动6个指针。每次修改都要有开销,但需要置换哪个页面却可直接得到,用不着查找,因为尾指针指向栈底,其中有被置换页。(这里就对应了那个链表顺序的方法,但是因为要快速访问(get),再次调整使用过的顺序,所以还是需要哈希映射链表中的地址的)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值