LRU算法与LFU算法

知识点:

LRU是Least Recently Used的缩写,意思是最近最少使用,它是一种Cache替换算法
Cache的容量有限,因此当Cache的容量用完后,而又有新的内容需要添加进来时, 就需要挑选
并舍弃原有的部分内容,从而腾出空间来放新内容。LRU Cache 的替换原则就是将最近最少使用
的内容替换掉。其实,LRU译成最久未使用会更形象, 因为该算法每次替换掉的就是一段时间内
最久没有使用过的内容

LRU本质就是一个页面置换算法,关键就是如何实现get()和put()函数O(1)时间复杂度

具体内容请打开leedcode上面这题:

146. LRU 缓存 - 力扣(LeetCode)

如何实现O(1),关键在于链表和哈希的使用,我来结合代码讲解:

cpp 复制代码
//首先,定义一个capacity、list和unordered_map成员变量,注意的是_hashmp存放的第二个参数为list迭代器,list存放的是pair类型,first为key,second为value
//其次:实现put函数,如果存在,我们只需要更新器内容,并放在链表的头部(注:尾部是不使用的),但当我们put的是不存在的,需要判断容量是否满足,是否需要删除list尾部,然后再插入到链表的头部,更新------_hashmp注意放的是iterator
//最后,实现get函数,不存在返回-1,否则,找到该value,然后将其提前保存放入list头部,再将其原位置删除,同时更新hash中迭代器位置
class LRUCache {
public:
    LRUCache(int capacity) 
    {
        _capacity = capacity;
    }
    //O(1):
    int get(int key) 
    {
        //通过key得到value,否则返回-1
        //去哈希中查找
        auto hashit = _hashmap.find(key);
        if(hashit == _hashmap.end())
        {
            //未找到
            return -1;
        }
        else
        {
            //找到
            auto listit = hashit->second;
            //更新该节点的使用情况
            pair<int,int> kv = *listit;
            _list.erase(listit);
            _list.push_front(kv);
            //更新哈希
            _hashmap[key] = _list.begin();
            return kv.second;
        }
    }
    //O(1):
    void put(int key, int value) 
    {
        //存在更新,不存在插入
        //判断是否存在
        auto hashit = _hashmap.find(key);//注意:hashit是迭代器
        if(hashit == _hashmap.end())
        {
            //不存在,插入,注意capacity
            if(_list.size() == _capacity)
            {
                //删除最近未使用的
                _hashmap.erase(_list.back().first);
                _list.pop_back();
            }
            //插入到链表头部+更新哈希
            _list.push_front(make_pair(key,value));
            _hashmap[key] = _list.begin();
        }
        else
        {
            //存在,更新数据并且放在链表头部,我们尾部是最近未使用数据
            //获取list中迭代器位置
            auto listit = hashit->second;
            //更新KV
            pair<int,int> kv = *listit;
            kv.second = value;
            //将链表中该节点位置放入头部
            _list.erase(listit);
            _list.push_front(kv);
            //更新哈希
            _hashmap[key] = _list.begin();
        }
    }
private:
    int _capacity;//容量
    list<pair<int,int>> _list;
    unordered_map<int,list<pair<int,int>>::iterator> _hashmap;
};

LRU面试不算经常考察的内容,但是也需要我们会实现到O(1)的复杂度

补充:

现在还存在面试常考的另一种算法LFU算法

该算法是通过一个count记录来处理最少使用的情况,下面联系leedcode例题来讲解:

460. LFU 缓存 - 力扣(LeetCode)

实现过程:

cpp 复制代码
class LFUCache {
public:
    LFUCache(int capacity) {
        _capacity = capacity;
    }
    
    int get(int key) {
        auto hashit = _hashmp.find(key);
        if (hashit == _hashmp.end()) return -1;

        // 获取当前节点并更新频率
        auto listit = hashit->second;
        auto kcv = *listit;
        _list.erase(listit);  // 先移除旧节点

        // 更新频率并重新插入到正确位置
        kcv.second.first++;
        auto new_it = insertIntoSortedList(kcv);
        _hashmp[key] = new_it;

        return kcv.second.second;
    }
    
    void put(int key, int value) {
        auto hashit = _hashmp.find(key);
        if (hashit == _hashmp.end()) 
        {
            // 缓存已满,淘汰频率最低且最久未使用的节点
            if (_list.size() >= _capacity) 
            {
                _hashmp.erase(_list.back().first);
                _list.pop_back();
            }
            // 插入新节点(频率=1)
            auto kcv = make_pair(key, make_pair(1, value));
            auto new_it = insertIntoSortedList(kcv);
            _hashmp[key] = new_it;
        } 
        else 
        {
            // 更新现有节点(逻辑同get)
            auto listit = hashit->second;
            auto kcv = *listit;
            _list.erase(listit);

            kcv.second.first++;
            kcv.second.second = value;  // 更新value
            auto new_it = insertIntoSortedList(kcv);
            _hashmp[key] = new_it;
        }
    }

private:
    // 辅助函数:将节点按频率和访问顺序插入到链表
    list<pair<int, pair<int, int>>>::iterator insertIntoSortedList(const pair<int, pair<int, int>>& kcv) 
    {
        // 遍历链表,找到第一个频率 <= 当前节点频率的位置
        for (auto it = _list.begin(); it != _list.end(); ++it) 
        {
            if (it->second.first <= kcv.second.first) {
                return _list.insert(it, kcv);  // 插入到该位置前
            }
        }
        // 如果所有节点频率都更低,插入到末尾
        return _list.insert(_list.end(), kcv);
    }
    int _capacity;
    list<pair<int, pair<int, int>>> _list;  // (key, (count, value))
    unordered_map<int, list<pair<int, pair<int, int>>>::iterator> _hashmp;
};

通过list和unordered_map来实现,但是需要两个pair(重点),另外就是就是insert和erase操作使用,希望对大家有帮助!!!