leetcode: LRU(最近最少使用)算法

                                                               True life is lived when tiny changes occur.  -- 列夫 托尔斯泰


前几天看到了这个题目,随然没有仔细看细节,但是知道了这种思想,对我的帮助非常大。

leetcode链接:https://leetcode.com/problems/lru-cache/:

讨论区里的一种解法:kick me ,借鉴这种方法,自己修改后:

思路:

1) 作者的关于为什么采用list这种结构 的解释: the good thing about lists is that iterators are never invalidated by modifiers (unless erasing the element itself). This way, we can store the iterator to the corresponding LRU queue in the values of the hash map. Since using erase on a list with an iterator takes constant time, all operations of the LRU cache run in constant time.

2)整体思路:用list来保存一个个节点,每个节点都是一个键值pair。然后,list的头部总是保存最近被操作过的节点。但是,要从list中删除元素,如果采用遍历的方法,十分地耗时,因此再使用一个map将list中各个节点的对应的iterator(相当于地址)进行保存,这样删除时就不用进行遍历了。 list中进行添加节点时,只是list的头部进行添加,因此很快。

code:

class LRUCache {
public:
    LRUCache(int capacity) : _capacity(capacity) {}

    int get(int key) {
        auto it = cache.find(key);
        if (it == cache.end())
            return -1;

        touch(it->second,it->second->second);//it->first是map的key值哦
        return it->second->second;
    }

    void put(int key, int value) {
        auto it = cache.find(key);
        if (it != cache.end()){
            touch(it->second,value);
        }
        else {

            if (cache.size() == _capacity) {
                cache.erase(used.back().first);
                used.pop_back();
            }
            Pair_KeyVal pair(key,value);
            used.push_front(pair);
            cache[key] =  used.begin();
        }
    }

private:
    typedef pair<int, int> Pair_KeyVal;
    //定义一种list,其每一个节点都是一个 key-val pair
    //用list的原因在于可以控制在其头尾进行操作,头部是最近刚操纵过的
    typedef list<Pair_KeyVal> List_KeyValPair; 

    //定义一个unordered_map,其key值为list的节点的key值,其val为list的节点的iterator(相当于将list节点的地址保存下来)
    //使用unordered_map可以首先快速检索。 
    typedef unordered_map<int, List_KeyValPair::iterator> Map_Key_ListIter;

    void touch(List_KeyValPair::iterator it,int setVal) {

        int key = it->first;
        used.erase(it);
        Pair_KeyVal pair(key,setVal);
        used.push_front(pair);

        cache[key] = used.begin();
    }

    Map_Key_ListIter cache;
    List_KeyValPair used;
    int _capacity;
};

关于c++中的map:

hash_map未加入在C++11标准中,因此使用了unordered_map。

c++ map内部实现了一个红黑树,该结构具有自动排序的功能,因此map内部的所有元素都是有序的,红黑树的每一个节点都代表着map的一个元素,因此,对于map进行的查找,删除,添加等一系列的操作都相当于是对红黑树进行这样的操作,故红黑树的效率决定了map的效率。


c++  unordered_map内部实现了一个哈希表,因此其元素的排列顺序是杂乱的,无序的。 优点:因为内部实现了哈希表,因此其查找速度非常快。对于查找问题,unordered_map会更加高效一些。 https://zh.cppreference.com/w/cpp/container/unordered_map


这种思想的一种简单场景:- interview

实现一个文本编辑器,可以实现插入,撤销操作。其中,撤销操作需要你保存最近操作过的字符,当用户点击撤销键时,将其最近添加或删除的一个字符进行回退!

 

2020.10再次练习:

Note:使用std::list很方便,曾尝试使用自己实现的双向链表,相当麻烦,在短时间内不回写好。

Note:现在明白了stl中的iterator其实是对指针的封装,理解的更清楚了。

Note:注意对list的尾部元素进行处理时,使用list的pop_back, back函数,,very 方便。比先将iterator定位到最后(常常易于出错),然后删除快。


#pragma once
#include <iostream>
#include <list>
#include <map>
#include <unordered_map>
#include <utility>

using namespace std;

class LRUCache
{
public:
    
    LRUCache():mCapacity(0), mCurrSize(0)
    {

    }
    LRUCache(int cap) :mCapacity(cap), mCurrSize(0)
    {

    }
    ~LRUCache() {};

    //    //
    int get(int key) {
        if (mCapacity < 1 || mCurrSize<1)
            return -1;

        if (mapContain.find(key) != mapContain.end()) {
            int res = mapContain[key]->second;
            //move this item to the top of the list
            listContain.erase(mapContain[key]);//先删除    
            listContain.push_front(make_pair(key, res));//置到头部
            mapContain[key] = listContain.begin();//更新map
            return res;
        }
        else
            return -1;
    }

    void put(int key, int val) {
        if (mCapacity < 1)
            return;
        if (mapContain.find(key) != mapContain.end()) {//key值已经存在
            //move this item to the top of the list
            listContain.erase(mapContain[key]);//先删除    
            listContain.push_front(make_pair(key, val));//置到头部
            mapContain[key] = listContain.begin();//更新map
        }
        else {
            if (mCurrSize < mCapacity) {
                listContain.push_front(make_pair(key, val));
                mapContain[key] = listContain.begin();//
                mCurrSize++;
            }
            else {
                //mCurrSize = mCapacity 且 key不在map中:
                listContain.push_front(make_pair(key, val));//将新数对放到头部
                mapContain[key] = listContain.begin();

                //删掉list中的最后一个元素
                //Method 1: wrong!
                //mapContain.erase(listContain.end()->first);//删掉最后一个. listContain.end()为空!不能酱。
              
                //Method 2: ok, 将迭代器定位到最后一个元素, std::advance耗时!
                //list<pair<int, int>>::iterator iter = listContain.begin();
                //std::advance(iter, listContain.size() - 1); //iter is set to last element
                //mapContain.erase(iter->first);//删掉最后一个
                //listContain.erase(iter);

                //Method 2: ok, 将迭代器定位到最后一个元素,使用--end()!
                //list<pair<int, int>>::iterator iterLast = --listContain.end();
                //mapContain.erase(iterLast->first);//删掉最后一个
                //listContain.erase(iterLast);

                //Method 3: ok --最快
                mapContain.erase(listContain.back().first);//删掉最后一个
                listContain.pop_back(); 
            }
        }

       
    }

private:
    int mCapacity;
    int mCurrSize;
    //保存一个个数(key,val)对到list中, 利用list的链子性保存存取顺序:
    list<pair<int, int>> listContain; 

    //map的key为数对的key, map的val为数对在list中的地址,保存地址是为了快速从list中取出,规避了list不能随机
    //存取的缺点,代价是多了个map的空间。 嗯,iterator本质是对指针的封装。
    unordered_map<int, list<pair<int, int>>::iterator> mapContain;

    /    //
};

void main() {

    //list's basic operation:
    list<int> listCache;
    listCache.push_back(1);
    listCache.push_back(2);
    listCache.push_back(3);
    listCache.push_front(0);

    list<int>::iterator it = find(listCache.begin(),listCache.end(),2);
    if(it != listCache.end())
        listCache.erase(it);

    //map's basic operation:
    unordered_map<int, float> mapTemp;
    mapTemp[0] = 0.1;
    mapTemp[1] = 1.1;
    mapTemp[2] = 2.1;
    if (mapTemp.find(2) != mapTemp.end())
        mapTemp.erase(2);

    int retu = 0;
    //LRUCache cache(2);
    //cache.put(1, 1);
    //cache.put(2, 2);
    //retu = cache.get(1);       // 返回  1
    //cache.put(3, 3);    // 该操作会使得关键字 2 作废
    //retu = cache.get(2);       // 返回 -1 (未找到)
    //cache.put(4, 4);    // 该操作会使得关键字 1 作废
    //retu = cache.get(1);       // 返回 -1 (未找到)
    //retu = cache.get(3);       // 返回  3
    //retu = cache.get(4);       // 返回  4

    LRUCache cache(1);
    cache.put(2, 1);
    retu = cache.get(2);
    cache.put(3, 2);
    retu = cache.get(2);
    retu = cache.get(3);
    return;
}



 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

First Snowflakes

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值