LRU缓存机制实现

运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put 。

获取数据 get(key) - 如果密钥 (key) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1。
写入数据 put(key, value) - 如果密钥不存在,则写入其数据值。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。

进阶:
你是否可以在 O(1) 时间复杂度内完成这两种操作?

实例:
LRUCache cache = new LRUCache( 2 /* 缓存容量 */ );

cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // 返回 1
cache.put(3, 3); // 该操作会使得密钥 2 作废
cache.get(2); // 返回 -1 (未找到)
cache.put(4, 4); // 该操作会使得密钥 1 作废
cache.get(1); // 返回 -1 (未找到)
cache.get(3); // 返回 3
cache.get(4); // 返回 4

LRU(Least Recently Used),最近最少使用算法,是一种常用的页面置换算法,用来最大化页面命中率。选择最近最久未使用的页面予以淘汰。该算法赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来所经历的时间t,当必须淘汰一个页面时,选择现有页面中t值最大的,即最近最少使用的页面予以淘汰。(来自百度百科

题目要求实现LRU缓存机制,需要在O(1)时间内完成:

  1. 获取键/检查键是否存在;
  2. 设置键;
  3. 删除最先插入的键;
    前两个操作可以使用标准的哈希表在O(1)时间内完成。

LRU算法实际上是让你设计数据结构:首先要接收一个capacity参数作为缓存的最大容量,然后实现两个API,一个是**put(key, val)方法存入键值对,另一个是get(key)**方法获取key对应的val,如果key不存在则返回-1。并且get和put方法都应该是O(1)时间复杂度

方法一:有序字典(Python OrderedDict)

使用Python中的OrderedDict(有序字典)来实现。

from collections import OrderedDict

class LRUCache(OrderedDict):

    def __init__(self, capacity: int):
        self.capacity = capacity


    def get(self, key: int) -> int:
        if key not in self:
            return -1

        self.move_to_end(key) # 把当前的键值移动到最后端
        return self[key]


    def put(self, key: int, value: int) -> None:
        if key in self:
            self.move_to_end(key)
        self[key] = value
        if len(self) > self.capacity:
            self.popitem(last=False) # 如果超出了容量,从最前端弹出元素(最久未使用的);

方法二:哈希链表(C++)

为了满足插入块,查找快,删除快,有顺序之分的特点,使用哈希表和双向链表组合而成的哈希链表。哈希表满足了查找、插入、删除快的特性,而双向链表满足了有顺序的特点。链表的删除需要得到前一结点指向它的指针,双向链表可以快速得到这个指针,保证了删除操作也可以在O(1)时间复杂度内完成。因为删除时要同时删除key-value键值对,所以链表的结点一定要同时保存key值和val值,否则在删除时无法通过val值找到与之对应的key值。

/*
哈希表加双向链表
*/

class LRUCache {
    int cap; // 容量
    // 双向链表,储存(key, value)键值对;
    list<pair<int, int> > cache;
    // 哈希表:key映射到(key,value)在cache中的位置;
    unordered_map<int, list<pair<int, int> >::iterator > hashmap;
public:
    LRUCache(int capacity) {
        // 设定容量
        this->cap = capacity;
    }
    
    int get(int key) {
        auto it = hashmap.find(key); //it是一个迭代器;
        // 访问的key不存在
        if (it == hashmap.end()) return -1;
        // key存在,把(k,v)换到队头(使用先删除再插入的方式)
        pair<int, int> kv = *hashmap[key];
        cache.erase(hashmap[key]);
        cache.push_front(kv);
        // 更新(key,value)在cache中的位置;
        hashmap[key] = cache.begin();
        return kv.second; // 返回value值;
    }
    
    void put(int key, int value) {
        // 要首先判断key是否已经存在
        auto it = hashmap.find(key);
        if (it == hashmap.end()){
            // 当前key在cache中不存在,需要进行插入
            // 判断cache是否已满
            if (cache.size() == this->cap) {
                // cache已满,删除尾部的键值对腾位置
                // cache和map中的数据都要删除
                // 删除cache末尾的键值对
                auto lastPair = cache.back();
                int lastKey = lastPair.first;
                hashmap.erase(lastKey);
                cache.pop_back();
            }
            // cache没满,可以直接添加
            cache.push_front(make_pair(key, value));
           hashmap[key] = cache.begin();
        }
        else {
            // key存在,更改value并换到队头
            cache.erase(hashmap[key]);
            cache.push_front(make_pair(key, value));
            hashmap[key] = cache.begin();
        }
    }
};

/**
 * 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);
 */
发布了76 篇原创文章 · 获赞 10 · 访问量 8238

猜你喜欢

转载自blog.csdn.net/weixin_38742280/article/details/105312431