面试集锦-------LRU,LFU手撕算法

LRU(最近最久未使用算法)

在这里插入图片描述

思路:

这里用到了两个数据结构,一个是list,另一个是unordeed_map
我们的思路也很简单,在map中以key为键,值得话我们取相对应下list存储key的iterator,为什么呢?主要是我们可以利用map的O(1)的查找,如果能找得到,就表示List是存在这样的key的。 这里,我们以list的队尾为最近最久未使用的元素节点,也就是说队头的一定是刚刚使用过的节点。

用一个结构体作为list的元素节点,node里面必须存储key和value !
为什么要把key存储下来呢?那是因为我们在map删除的时候,是要用到key的,通过key来得到list下的iterator,这样我们才能删除成功。
map元素的值其实就是list对应下key的iterator,这个很重要,理解了这个,这道题就完成了。

struct node{
    
    
    int key,value;
    node(int a,int b):key(a),value(b){
    
    }
};

class LRUCache {
    
    
public:
    LRUCache(int capacity) {
    
    
        _size = capacity;
        lt.clear();
        mp.clear();
    }
    
    int get(int key) {
    
    
        if(mp.find(key)==mp.end()){
    
        //如果没有找到,则没有
            return -1;
        }
        lt.splice(lt.begin(),lt,mp[key]);   //这个内置函数后面会讲到
        mp[key] = lt.begin();   //进行map元素的更新
        return mp[key]->value;  //返回查找的值
    }
    
    void put(int key, int value) {
    
    
        if(mp.find(key)==mp.end()){
    
        //没有这个值
            if(lt.size()==_size){
    
       //进行插入时需要判断list存储的容量是否超标,是的话就要进行页面换出的决策
                mp.erase(lt.back().key);   //在map删除换出元素的记录
                lt.pop_back();   //在list删除换出元素的记录
            }
            lt.push_front(node(key,value));   //在队头添加,表示刚刚用到的节点
            mp[key] = lt.begin();   //存储key 以及对应的iterator
        }
        else{
    
          //找到这个值,那么只需要进行更新即可
        		   //最重要的一点就是要把刚刚用到的节点放到队头上,这样才符合LRU算法思想,还有记得也要更新 key下的map元素的值。
            mp[key]->value = value;
            lt.splice(lt.begin(),lt,mp[key]);
            mp[key] = lt.begin();
        }
    }
private:
    list<node> lt;
    unordered_map<int,list<node>::iterator> mp;
    int _size;
};

/**
 * 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);
 */


### LFU(最近最少未使用算法)

在这里插入图片描述
版本一:

在这里插入图片描述

在这里插入图片描述
来自力扣的官方解释

代码:
struct node{
    
    
    int cnt,time,key,value;
    node(int a,int b,int c,int d):cnt(a),time(b),key(c),value(d){
    
    }
};

struct cmp{
    
    
    bool operator()(const node& a,const node& b)const{
    
    
        if(a.cnt==b.cnt){
    
    
            return a.time<b.time;
        }
        return a.cnt<b.cnt;
    }
};


class LFUCache {
    
    
public:
    LFUCache(int capacity) {
    
    
        _size =  capacity;
        _time = 0;
        mp.clear();
        st.clear();
    }
    
    int get(int key) {
    
    
        if(_size==0) return -1;
        auto it = mp.find(key);
        if(it==mp.end()) return -1;
        node op = it->second;
        st.erase(st.find(op));
        ++op.cnt;
        op.time = ++_time;
        st.insert(op);
        it->second = op;
        return op.value;
        
    }
    
    void put(int key, int value) {
    
    
        if(_size==0) return ;
        auto it = mp.find(key);
        if(it==mp.end()){
    
    
            if(_size==st.size()){
    
    
                mp.erase(st.begin()->key);
                st.erase(st.begin());
            }
            node op = node(1,++_time,key,value);
            st.insert(op);
            mp.insert(make_pair(key,op));
        }
        else{
    
    
            node bk = it->second;
            st.erase(bk);
            bk.cnt += 1;
            bk.time = ++_time;
            bk.value = value;
            it->second = bk;
            st.insert(bk);
        }
    }

private:
    unordered_map<int,node> mp;
    set<node,cmp> st;
    int _time;
    int _size;
};

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

版本二:

思路:

  1. 这里主要是用到了两个unordered_map数据结构,其底层实现就是hash_map,主要可以近似的认为存取查找操作O(1)的时间复杂度。
  2. key_map的类型是<key,list< node >::iterator>,这里的作用跟版本的map是一样的,主要是看看是否存在key元素,至于为什么存储的是iterator呢?那是因为我们可以再利用preq_map(其类型是< preq ,list< ndoe >>)去寻找list链表存储的key对应的位置,方便删除操作。
  3. preq_map数据结构里为什么存储的是list链表呢?版本一是怎么确认最近最少使用的页面?我们是不是用到了一个时间戳,这样在排序的时候就可以当频率一样时看时间戳即可。那么在这里呢?怎么确定?
    在这里,我们就是根据preq(在LFU类里的那个preq)这个变量,这样我们就知道当前最少次数的是哪些了,前提是我们必须给这些页面一个良好的管理,把出现相同preq次数一样的页面,用一个list链表进行存储,那怎么确定相同次数下哪个页面是最早的呢?其实很简单,我们只需要定一个规范,把刚访问的页面放在list链表的头,尾巴不就是最早的吗?(也就是要被淘汰的页面)
  4. 剩下的就是细节处理,具体看代码。

代码:

struct node{
    
    
    int key,value;
    int preq;    //频率,即次数
    node(int a,int b,int c):key(a),value(b),preq(c){
    
    }
};

class LFUCache {
    
    
public:
    LFUCache(int capacity) {
    
    
        minpreq = 0;
        _size =  capacity;
        key_map.clear();
        preq_map.clear();
    }
    
    int get(int key) {
    
    
        if(_size==0) return -1;
        auto it = key_map.find(key);
        if(it==key_map.end()) return -1;

        int value = it->second->value;
        int preq = it->second->preq;
        preq_map[preq].erase(it->second);
        if(preq_map[preq].size()==0 ){
    
    
            preq_map.erase(preq);
            if(minpreq==preq){
    
    
                minpreq += 1;
            }
        }
        preq_map[preq+1].push_front(node(key,value,preq+1));
        key_map[key] = preq_map[preq+1].begin();
        return value;
    }
    
    void put(int key, int value) {
    
    
        if(_size==0) return;
        auto it = key_map.find(key);  //先看看是否有这个key存在
        if(it==key_map.end()){
    
      //不存在
            if(key_map.size() == _size){
    
      //如果超过了内存的页面数量,那么就要进行置换
                node& it = preq_map[minpreq].back();  //这一步的操作就是为了能在key_map中删除对应key的记录
                key_map.erase(it.key);
                preq_map[minpreq].pop_back();  //通过私有变量minpreq进行preq_map上对应list的删除应该要被换出的页面
                if(preq_map[minpreq].empty()){
    
      //如果该minpreq变量下的list刚好被删除到为空,这里最好就把minpreq对应的list的链表删除
                    preq_map.erase(minpreq);
                }
            }
            minpreq = 1;  //很明显,当前的minpreq等于1
            preq_map[1].push_front(node(key,value,1));  //分别在两个map添加记录
            key_map[key] = preq_map[1].begin();
        }else{
    
       //存在
            auto _iterator = it->second;
            int preq = _iterator->preq;
            preq_map[preq].erase(_iterator);  //这里的步骤就是先把原先的preq下的那个旧记录删除,然后再添加新元素(其实也不能说是新元素,应该是新的记录,只要是为了更新preq频率)
            if(preq_map[preq].empty()){
    
    
                preq_map.erase(preq);
                if(minpreq==preq){
    
    
                    minpreq += 1;
                }
            }
            preq_map[preq+1].push_front(node(key,value,preq+1));
            key_map[key] = preq_map[preq+1].begin();
        }
    }

private:
    int minpreq;
    int _size;
    unordered_map<int,list<node>::iterator> key_map;
    unordered_map<int,list<node> > preq_map;
};

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

猜你喜欢

转载自blog.csdn.net/weixin_43743711/article/details/112252298