设计实现LRU缓存机制

一、要求

LRU:最近最少使用算法

  1. 支持:getput 操作
    • get(key): 获取数据 get, 如果密钥 (key) 存在于缓存中,则返回对应的值
    • put(key, value): 如果密钥不存在,则写入其数据值。当缓存容量达到上限时,它应该在写入新数据之前删除最近最少未使用的数据值,从而为新的数据值留出空间。
  2. getput 时间复杂度 O(1)
  3. 题目:https://leetcode-cn.com/problems/lru-cache/

二、实现

1. 说明

其实标准的 hash 数据结构可以实现get 操作 O(1), 但是针对put 操作的中,`到达内存容量上限是,删除最近最少使用的数据值的, 简单hash 无法做到O(1),因为无法判断那些数据是最近最少使用的。我们采取一个双向链表去维护LRU的数据值。

双向链表

如图所示,我们建立一个双向的链表去维持LRU 这么一个概念,通过头节点尾节点去维护这个双向链表。 我们每次操作一个节点(get, put)的时候,都把这个节点移到头节点后面。这样经过一系列的操作后,链表除了尾节点的最后一个就是最近最少使用的数据值。因为凡是被操作过的节点都是插入到头节点后面,未被操作的节点慢慢就挪到了尾部。

2. 实现hash

这里面我们采取的C++ STL中的unordered_map作为hash结构的实现。

3. 实现双向链表
  • 链表的节点数据结构
template<typename K, typename V>
struct BiNode {
    K key;
    V value;
    BiNode(int k, int v = 0):key(k), value(v){};
    shared_ptr<BiNode> pre;
    shared_ptr<BiNode> next;
};

template<typename K, typename V>
bool operator==(const BiNode<K, V>& node1, const BiNode<K, V>& node2){
    return node1.key == node2.key;
}

namespace std {

    template<typename K, typename V>
    struct hash<BiNode<K, V>> {
        size_t operator()(const BiNode<K, V>& node) const noexcept {
            return std::hash<size_t>()(node.key);
        }
    };
}

应为我们会在unorderd_map中 存储链表,所以要重载等于(==)和hash,使节点可以计算哈希值和判断散列冲突。

  • 实现维护LRU的双向链表
template<typename K, typename V>
class LRUList {
public:
    using NodeType = BiNode<K, V>;
    using PNodeType = shared_ptr<NodeType>;

    LinkedList(){
        head_ = make_shared<NodeType>();
        tail_ = make_shared<NodeType>();
        head_->next = tail_;
        tail_->pre = head_;
    }

    PNodeType add(K key, V value){
        PNodeType node = make_shared<NodeType>(key, value);
        PNodeType next = head_->next;
        node->next = next;
        next->pre = node;
        head_->next = node;
        node->pre = head_;
    }

    PNodeType pop(){
        PNodeType target = tail_->pre;
        if(target == head_)
            return NullNode;
        PNodeType pre = target->pre;
        pre->next = tail_;
        tail_->pre = pre;
        return target;
    }

    PNodeType move_to_head(PNodeType node){
        PNodeType pre  = node->pre;
        PNodeType next = node->next;
        pre->next = next;
        next->pre = pre;

        next = head_->next;
        
        node->next = next;
        next->pre = node;
        node->pre = head_;
        head_->next = node;
    }

public:
    static const PNodeType NullNode = make_shared<NodeType>();
private:
    PNodeType head_;
    PNodeType tail_;
};

可以看到我们所有操作都是通过头节点和尾节点实现的,所以我们这个链表的操作的复杂度都是O(1)的。

三、LRU 缓存机制

最终我们组合unordered_map(哈希)和 LRUList (双向链表)实现 LRU 缓存机制。

  • get(key): 获取的时候我们通过unordered_map直接获得对应的值,O(1)
  • put(key, value): 插入数据时候如果键值存在,直接插入。如果不存在,内存足够时,直接将节点插入unordered_mapLRUList中即可;不足时,现在LRUList中获取最近最实用的值,把其在哈希表和双向链表中删除,再插入新的节点。复杂度O(1)

template<typename K, typename V>
class LRUCache {
public:
    using PNodeType = shared_ptr<BiNode<K, V>>;
public:

    LRUCache(int cap):cap_(cap){};
    PNodeType get(int key){
        if(dict_.count(key)){
            PNodeType node = dict_[key];
            list_.move_to_head(node);
            return node;    
        }
        
        return LRUList<K, V>::NullNode;
    }

    void set(int key, int val){
        PNodeType cur = nullptr;
        
        if(dict_.count(key)){
            cur = dict_[key];
            cur->value = val;
        }else{
            if(dict_.size() == cap_){
                auto del_node = list_.pop();
                dict_.erase(del_node->key);
            }
            cur = list_.add(key, val);
            dict_[key] = cur;
        }
        list_.move_to_head(cur);
    }

private:
    size_t cap_ = 0;
    LRUList<K, V> list_ = {};
    unordered_map<K, BiNode<K, V>> dict_ = {};
};


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,045评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,114评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,120评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,902评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,828评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,132评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,590评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,258评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,408评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,335评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,385评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,068评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,660评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,747评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,967评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,406评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,970评论 2 341