C++实现LRU缓存机制

leetcode题目链接

1.题目描述

运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制

实现 LRU Cache 类:

  • LRUCache(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存
  • int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
  • void put(int key, int value) 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字-值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。

2.实现思路

栈+map
or
双链表+map

get(key):直接获取map元素,并将被访问元素置于栈顶
put(key):元素入栈,若栈满,删除栈底元素;若更新元素,将元素置于栈顶,并更新map对应value

双链表模拟栈的优势:链头模拟栈底,链尾模拟栈顶,剪接节点置于栈顶的操作只需要O(1)复杂度

3.代码

class LRUCache {
    
    
private:
    int capasize_;
    list<pair<int,int>>cache;//双向链表
    unordered_map<int,list<pair<int,int>>::iterator> mp;//哈希表

public:
    //构造函数
    LRUCache(int capacity) {
    
    
        capasize_=capacity;
    }

    int get(int key) {
    
    
        const auto it=mp.find(key);
        //判断key是否存在哈希表中
        if(it==mp.cend())
            return -1;//不存在返回-1
        //如果存在则将双向链表中含关键字key的节点移至头部
        cache.splice(cache.begin(),cache,it->second);
        //返回value
        return it->second->second; 
    }

    void put(int key, int value) {
    
    
        const auto it=mp.find(key);
        //判断key是否存在哈希表中,存在则改变value,然后将双向链表中含关键字key的节点移至头部
        if(it!=mp.cend())
        {
    
    
            it->second->second=value;
            cache.splice(cache.begin(),cache,it->second);
            return;//记得return
        }
        //如果不存在则要判断此时双向链表容量是否已满
        //若满了则要去除双向链表中的尾节点以及哈希表中key关键字位置
        if(cache.size()==capasize_)
        {
    
    
            const auto&node=cache.back();
            mp.erase(node.first);
            cache.pop_back();
        }
        //新建一个节点将其插入到链表头部
        cache.emplace_front(key,value);
        //让哈希表中key关键字指向链表新的头节点
        mp[key]=cache.begin();
    }
};

4.STL的list学习

注意:STL中的list是双向链表。

list::splice实现list拼接的功能。将源list的内容部分或全部元素删除,拼插入到目的list。

函数有以下三种声明:

一:void splice ( iterator position, list<T,Allocator>& x );

二:void splice ( iterator position, list<T,Allocator>& x, iterator it );

三:void splice ( iterator position, list<T,Allocator>& x, iterator first, iterator last );

解释:


position 是要操作的list对象的迭代器

list&x 被剪的对象

对于一:会在position后把list&x所有的元素到剪接到要操作的list对象
对于二:只会把it的值剪接到要操作的list对象中
对于三:把first 到 last 剪接到要操作的list对象中

5.拓展:实现LFU(最不经常使用缓存算法)

leetcode题目链接

请你为 最不经常使用(LFU)缓存算法设计并实现数据结构。

实现 LFUCache 类:

  • LFUCache(int capacity) - 用数据结构的容量 capacity 初始化对象
  • int get(int key) - 如果键存在于缓存中,则获取键的值,否则返回 -1。
  • void put(int key, int value) - 如果键已存在,则变更其值;如果键不存在,请插入键值对。当缓存达到其容量时,则应该在插入新项之前,使最不经常使用的项无效。在此问题中,当存在平局(即两个或更多个键具有相同使用频率)时,应该去除 最久未使用 的键。
class LFUCache {
    
    
private:
    // 双向链表节点
    struct DLinkNode {
    
    
        int key, val, freq;

        // 节点构造函数
        DLinkNode(int key, int val, int freq) : key(key), val(val), freq(freq) {
    
    }
    };

    // 最小频率,表示最近最少使用的链表
    int minFreq;
    // 缓存的容量
    int capacity;

    // 哈希表:存储(key, 链表节点在链表中的位置)
    std::unordered_map<int, std::list<DLinkNode>::iterator> key_table;
    // 哈希表:存储(key, val, freq)
    std::unordered_map<int, std::list<DLinkNode>> freq_table;

public:

LFUCache(int capacity) {
    
    
    this->minFreq = 0;
    this->capacity = capacity;
    key_table.clear();
    freq_table.clear();
}

int get(int key) {
    
    
    if (capacity == 0) {
    
    
        return -1;
    }

    // key_table中是否存在该key
    auto iter = key_table.find(key);
    // 如果不存在,返回-1
    if (iter == key_table.end()) {
    
    
        return -1;
    }

    // 如果存在该key,
    // node为iter指向的节点,即key对应的节点
    auto node = iter->second;
    // 该节点的val和freq值
    int val = node->val;
    int freq = node->freq;
    // 因为get(key)属于操作,所以该节点频率加1
    // 将该节点从哈希表原链表freq_table[freq]中删除
    freq_table[freq].erase(node);

    // 如果删除该节点后,freq_table[freq]链表为空
    if (freq_table[freq].empty()) {
    
    
        // 则从freq_table中删除该频率freq对应的双向链表
        freq_table.erase(freq);
        // 如果最小频率minFreq等于freq,因为freq使用后频率加1,
        // 所以minFreq最小频率也要更新为加1
        if (minFreq == freq) {
    
    
            minFreq += 1;
        }
    }

    // 头部表示最近使用,尾部表示最近最久未使用
    // 将该节点添加到链表freq_table[freq+1]的头部,并将该节点的频率更新
    freq_table[freq + 1].push_front(DLinkNode(key, val, freq + 1));
    // 同时更新key_table中key所对应节点在新链表freq_table[freq+1]的头部位置
    key_table[key] = freq_table[freq + 1].begin();

    return val;
}

void put(int key, int value) {
    
    
    if (capacity == 0) {
    
    
        return;
    }

    // key_table中是否存在该key
    auto iter = key_table.find(key);

    // 如果不存在该key对应的节点,就将该节点插入到频率对应的双向链表头部
    if (iter == key_table.end()) {
    
    
        // 首先判断当前缓存大小是否达到容量
        if (key_table.size() == capacity) {
    
    
            // 如果当前缓存大小已经达到容量,则需要先删除最近最少使用的节点
            // 在哈希表中找到最近最少使用的双向链表freq_table[minFreq]
            // 因为最近加入的节点都在头部,所以最近最少使用的是尾部节点
            auto it = freq_table[minFreq].back();
            // 删除key_table中最近最少使用的节点
            key_table.erase(it.key);
            // 从双向链表freq_table[minFreq]中删除尾节点
            freq_table[minFreq].pop_back();

            // 判断此时频率最小的双向链表是否为空
            if (freq_table[minFreq].empty()) {
    
    
                // 如果为空则删除频率最小的双向链表
                freq_table.erase(minFreq);
            }
        }

        // 因为新插入节点的频率为1,所有将其插入到freq_table[1]链表中
        freq_table[1].push_front(DLinkNode(key, value, 1));
        // 同时更新key_table中key所对应的节点在频率链表中相应的位置
        key_table[key] = freq_table[1].begin();
        // 更新最小频率为1
        minFreq = 1;
    } else {
    
    
        // 如果存在该key,
        // node为iter指向的节点,即key对应的节点
        auto node = iter->second;
        // 获得该节点的freq值
        int freq = node->freq;
        // 因为该节点因使用后频率加1,所以从原频率链表中删除该节点
        freq_table[freq].erase(node);

        // 判断节点原频率链表是否为空
        if (freq_table[freq].empty()) {
    
    
            // 如果为空,则从哈希表中删除该频率链表
            freq_table.erase(freq);
            // 如果最小频率等于节点原频率,则将最小频率更新加1
            if (minFreq == freq) {
    
    
                minFreq += 1;
            }
        }

        // 将该节点频率加1后添加到频率加1的双向链表的头部
        freq_table[freq + 1].push_front(DLinkNode(key, value, freq + 1));
        // 同时更新key所对应的节点在相应频率链表中的头部位置
        key_table[key] = freq_table[freq + 1].begin();
    }
}

};

猜你喜欢

转载自blog.csdn.net/weixin_43202635/article/details/115265180
今日推荐