算法-模拟LRU缓存机制

题意

请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。 实现 LRUCache 类

  • LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存
  • int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1
  • void put(int key, int value) 如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ,则应该 逐出 最久未使用的关键字。

函数 get 和 put 必须以 O(1) 的平均时间复杂度运行。

题解

要求get和put都要以O(1)的平均时间复杂度运行。
已知:哈希表(unordered_map<int, MyNode*>)查找get(),增,改put()为O(1),但是无顺序,由题意看出我们需要记录进入缓存的先后。因此还需要一个数据结构来记录先后顺序。
可以实现一个双向链表来完成队列的作用,最新插入的可以在队尾插入,而达到容量需要删除的则是队首的元素。因此刚好可以用双向链表来完成队列模拟。
最关键的是模拟双向链表如何实现,因为哈希表可以直接使用unorder_map来实现。
1、定义节点的类:需要定义两个指针一个指向前、一个指向后

class MyNode
{
    public:
    int key;
    int val;
    MyNode* prev;
    MyNode* next;
    MyNode(): key(0), val(0), prev(nullptr), next(nullptr){}
    MyNode(int _key, int _val): key(_key), val(_val), prev(nullptr), next(nullptr){}
};

2、定义根据节点来实现的双向链表的功能:增、删头,改,删任意点的功能

  • 定义公有的变量:头结点指针、尾节点指针
  • 定义构造函数:初始化头结点和尾节点:在堆上面创建,并互相指。
  • 定义增节点函数,需要传入节点指针:增加就是在尾部增加,因此tail->prev才是尾节点其next指针指向新的节点,新节点的prev指针指向tail->prev(之前的尾节点),新节点的next指针指向tail尾巴,tail的prev指针指向新节点。
  • 定义删头节点函数:要先判断是否存在头结点,如果头结点不存在(if(head->next == tail->prev))则返回-1,否则删除头结点:先保存头结点的键用于擦除哈希表里面的节点,再用普通的删除节点函数删掉head->next节点。
  • 定义修改节点函数:删节点函数 + 增加节点函数
  • 定义删节点函数:传入删除的节点,让待删节点的前一个节点的next指针指向待删节点后一个节点。让待删节点后一个节点的prev指针指向待删节点的前一个节点即
node->prev->next = node->next;
node->next-prev = node->prev;
class Myqueue
{
    public:
    MyNode* head;
    MyNode* tail;
    Myqueue(){
        head = new MyNode();
        tail = new MyNode();
        head->next = tail;
        tail->prev = head;
    }

    void add_node(MyNode* node)
    {
        tail->prev->next = node;
        node->prev = tail->prev;
        node->next = tail;
        tail->prev = node;
    }
    int remove_head()
    {
        if(head->next == tail) return -1;
        int head_key = head->next->key;
        remove_node(head->next);
        return head_key;
    }
    void move_node(MyNode* node)
    {
        remove_node(node);
        add_node(node);
    }
    void remove_node(MyNode* node)
    {
        node->prev->next = node->next;
        node->next->prev = node->prev;
    }
};

3、定义好了我们需要的队列(双向链表),接下来就是使用它来完成我们的LRU缓存机制模拟:

  • 定义私有的变量:capacity、哈希表:unordered_map<int, MyNode*>、队列:Myqueue。(定义为私有变量的原因防止外部直接修改,保证缓存的一致性和正确性)
  • 在构造函数中,如果形参和成员变量同名,为了区分它们,可以使用 this 指针来引用当前对象的成员变量。this 指针指向当前对象本身,通过 this-> 可以访问当前对象的成员变量和成员函数。
  • 定义LRUCache的构造函数:获取capacity给与私有变量
  • get()函数是判断该key是否存在,不存在直接return -1,存在返回键对应的值。实现中:因为存在所以不需要再new一个变量,直接定义MyNode* node = map[key];即可以获取该点并操作该点。获取到该点我们依托私有变量的队列类中的mode_node(node)函数将当次访问过的点放到链尾上,并返回该节点记录的值
  • put()函数:传入了key和value的值,首先要判断该key是否已存在于缓存中,如果存在就调用queue类的功能move_node(node)函数。如果不存在就要判断是否map.size()已经达到了capacity了,达到了就得删除头节点并依据queue.removehead()返回的值删掉哈希表里面的值。在调用queue.add_node(node)函数。
class LRUCache {
private:
    int capacity;
    unordered_map<int, MyNode*> map;
    Myqueue queue;
public:
    LRUCache(int capacity) {
        this->capacity = capacity;
    }
    
    int get(int key) {
        if(!map.count(key)) return -1;
        MyNode* cur_node = map[key];
        queue.move_node(cur_node);
        return cur_node->val;        
    }
    
    void put(int key, int value) {
        MyNode* node;
        if(map.count(key))
        {
            node = map[key];
            node->val = value;
            queue.move_node(node);
        }
        else
        {
            if(map.size() == capacity)
            {
                int head_key = queue.remove_head();
                map.erase(head_key );
            }
            node = new MyNode(key, value);
            map[key] = node;
            queue.add_node(node);
        }
    }
};

总的代码

class MyNode
{
    public:
    int key;
    int val;
    MyNode* prev;
    MyNode* next;
    MyNode(): key(0), val(0), prev(nullptr), next(nullptr){}
    MyNode(int _key, int _val): key(_key), val(_val), prev(nullptr), next(nullptr){}
};

class Myqueue
{
    public:
    MyNode* head;
    MyNode* tail;
    Myqueue(){
        head = new MyNode();
        tail = new MyNode();
        head->next = tail;
        tail->prev = head;
    }

    void add_node(MyNode* node)
    {
        tail->prev->next = node;
        node->prev = tail->prev;
        node->next = tail;
        tail->prev = node;
    }
    int remove_head()
    {
        if(head->next == tail) return -1;
        int head_key = head->next->key;
        remove_node(head->next);
        return head_key;
    }
    void move_node(MyNode* node)
    {
        remove_node(node);
        add_node(node);
    }
    void remove_node(MyNode* node)
    {
        node->prev->next = node->next;
        node->next->prev = node->prev;
    }
};


class LRUCache {
private:
    int capacity;
    unordered_map<int, MyNode*> map;
    Myqueue queue;
public:
    LRUCache(int capacity) {
        this->capacity = capacity;
    }
    
    int get(int key) {
        if(!map.count(key)) return -1;
        MyNode* cur_node = map[key];
        queue.move_node(cur_node);
        return cur_node->val;        
    }
    
    void put(int key, int value) {
        MyNode* node;
        if(map.count(key))
        {
            node = map[key];
            node->val = value;
            queue.move_node(node);
        }
        else
        {
            if(map.size() == capacity)
            {
                int head_key = queue.remove_head();
                map.erase(head_key );
            }
            node = new MyNode(key, value);
            map[key] = node;
            queue.add_node(node);
        }
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值