LRU缓存机制的实现(C++版本)

1 篇文章 0 订阅
1 篇文章 0 订阅

1.题目简介(LeetCode 146)

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

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

特别要求: O(1) 时间复杂度内完成这两种操作

链接:LeetCode 146

示例:

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

2.解题思想

一般来说,用数组、链表都能解决这题,但是因为要求O(1)时间复杂度完成操作,那么数组、链表都不可以,数组插入删除慢,而链表查找慢,所以想到哈希表,查找为O(1),但是光有哈希表肯定不够,因为要频繁的插入删除元素,那么链表是最合适的,就可以将哈希表与链表结合来实现,这点确实很难想到。这里注意,链表要用双向链表,这样更便于操作!
如图
如图所示,双向链表的每个节点都是key-value对,而哈希表中的key映射到key对应那个节点,比如要get(4),首先看4是否在哈希表中,若不在直接返回-1,若在则快速定位到4对应的节点,取出里面的value 也就是8

下面分模块讲解:

2.1 get 功能详解

get(key),也就是获取key对应的value值,有以下两种情况

1)key不在哈希表中

这个简单,直接返回-1

2)key在哈希表中

步骤:
1.通过哈希表中的key映射,定位到链表中对应节点
2.因为这个节点此刻被访问了,所以将它变为新的链表头(表头是最新访问过的,表尾是最久没访问过的)
3.取出表头里的value值返回
具体如下图所示:
在这里插入图片描述

2.2 put 功能详解

put(key,value)就是实现把key-value对放进缓存,下面分情况讨论

1)key在哈希表中

这说明缓存中已经存在这个key了,那么现在要做的就是更新key对应的value值
步骤:
1.通过哈希表定位到key对应的节点
2.将节点中的value值更新为当前的value值
3.因为这个节点刚被更新,所以将它变为链表的表头
具体如下图所示
在这里插入图片描述

2)key不在哈希表中

这时需要将此key-value对插入链表中,并更新哈希表将此key映射加入。这里又分为两种情况:缓存未满和缓存已满
1>缓存未满
步骤:
1.直接从表头插入key-value对,并更新头节点为该插入节点
2.更新哈希表,将key映射加入进去
具体如图所示
在这里插入图片描述
2>缓存已满
因为缓存已经满了,现在需要从缓存中去除一个数据,再将新数据添加进去。那么问题来了,去除哪个呢?剪刀石头布,谁输谁出去吗。。。当然不是了,再回到这个链表结构,它是越靠近头节点的最近访问的的时间点越近,越靠近尾节点的距离上次被访问的时间越久,那么很显然尾结点是最久未访问的了,就将尾节点去除,再从头部加入新的数据吧!
步骤:
1.去除尾节点,并将其在哈希表中的对应key映射也去除
2.更新尾节点为原先尾节点前一个节点(这就是双向链表的好处)
3.从头部直接插入新的key-value节点,并更新头节点为当前节点
4.更新哈希表,加入key映射
具体如图所示:
在这里插入图片描述

3.C++代码实现

双向链表这块可以用STL自带的list,不过我是自己实现的的一个简单的能够满足这题要求的双向链表,哈希表使用STL自带的unordered_map,它的底层是用哈希表实现的。代码也是按照上面逻辑,分情况实现的,代码可能比较臃肿,不过跑出来结果还是可以接受的。

class LRUCache {
    struct node
    {
        int key;
        int value;
        struct node *pre;
        struct node *next;
        node(int k,int v):key(k),value(v),pre(NULL),next(NULL){}
    };
    int cap=0; //表示当前容量
    node *head,*tail;
    void push_front(node *cur) //头部插入
    {
        if(cap==0)
        {
            head=cur;
            tail=cur;
            cap++;
        }
        else
        {
            cur->next=head;
            head->pre=cur;
            head=cur;
            cap++;
        }	

    }

    void push_back(node *cur) //尾部插入
    {
        if(cap==0)
        {
            head=cur;
            tail=cur;
            cap++;
        }
        else
        {
            cur->pre=tail;
            tail->next=cur;
            tail=cur;
            cap++;
        }	
    }

    void pop_front() //头部弹出 
    {
        if(cap==0) return;
        node *p=head;
        head=p->next;
        delete p;
        cap--;
    }

    void pop_back() //尾部弹出 
    {
        if(cap==0) return;
        node *p=tail;
        tail=p->pre;
        delete p;
        cap--;
    }

    void sethead(node *p) //将p结点变为新的头部
    {
        if(p==head) return;
        else if(p==tail)
        {
            p->pre->next=NULL;
            tail=p->pre;
            p->next=head;
            head->pre=p;
            p->pre==NULL;
            head=p;
        }
        else
        {
            p->pre->next=p->next;
            p->next->pre=p->pre;
            p->next=head;
            head->pre=p;
            p->pre=NULL;
            head=p;
        }
    }
public:
    LRUCache(int capacity): m_capacity(capacity){
        
    }
    
    int get(int key) {
        if(!m.count(key)) return -1; //不在哈希表中,直接返回-1
        node *p=m[key];
		sethead(p);//提到链表头部
        return head->value;
    }
    
    void put(int key, int value) {
        if(!m.count(key)) //如果不在缓存中
		{
			node *cur=new node(key,value);
			m[key]=cur; //加入哈希表
			//1.容量已满,去掉尾结点从头插入
			if(cap==m_capacity)
			{
				m.erase(tail->key);//将要弹出的尾结点对应映射从哈希表中删除 
				pop_back();
				push_front(cur); //头部插入
			}
			//2.容量未满,直接头部插入
			else  push_front(cur); 
		}
		else //在缓存中 ,旧值更新 
		{
			node *p=m[key];
			p->value=value;
            sethead(p); //提到链表头部
		}
    }
private:
    int m_capacity;
    unordered_map<int,node*>m;
};

提交结果如下:

在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
LRU 缓存是一种常见的缓存淘汰算法,它的设计思想是将最近最少使用的数据从缓存中淘汰出去,以保证缓存的数据都是热点数据,从而提高缓存的命中率。 下面是一个基于双向链表和哈希表实现LRU 缓存C++ 设计实现: ```cpp #include <unordered_map> struct Node { int key; int value; Node* prev; Node* next; Node(int k, int v) : key(k), value(v), prev(nullptr), next(nullptr) {} }; class LRUCache { public: LRUCache(int capacity) : size_(capacity), head_(nullptr), tail_(nullptr) {} int get(int key) { auto iter = cache_.find(key); if (iter == cache_.end()) { return -1; } else { Node* node = iter->second; move_to_head(node); return node->value; } } void put(int key, int value) { auto iter = cache_.find(key); if (iter == cache_.end()) { Node* node = new Node(key, value); add_to_head(node); cache_[key] = node; if (cache_.size() > size_) { Node* tail = remove_tail(); cache_.erase(tail->key); delete tail; } } else { Node* node = iter->second; node->value = value; move_to_head(node); } } private: int size_; Node* head_; Node* tail_; std::unordered_map<int, Node*> cache_; void add_to_head(Node* node) { if (head_ == nullptr) { head_ = node; tail_ = node; } else { node->next = head_; head_->prev = node; head_ = node; } } void move_to_head(Node* node) { if (node == head_) { return; } else if (node == tail_) { tail_ = tail_->prev; tail_->next = nullptr; } else { node->prev->next = node->next; node->next->prev = node->prev; } node->next = head_; head_->prev = node; node->prev = nullptr; head_ = node; } Node* remove_tail() { Node* node = tail_; if (head_ == tail_) { head_ = nullptr; tail_ = nullptr; } else { tail_ = tail_->prev; tail_->next = nullptr; } return node; } }; ``` 在这个实现中,我们使用了一个双向链表保存缓存中的数据,并使用一个哈希表来提高查找效率。在 `get` 操作中,如果缓存中不存在目标数据则返回 -1,否则将目标数据移到链表头部并返回其值。在 `put` 操作中,如果缓存中不存在目标数据则创建一个新的节点并将其添加到链表头部,如果缓存已满则删除链表尾部的节点。如果缓存中已存在目标数据则将其值更新并将其移动到链表头部。 需要注意的是,在 `move_to_head` 和 `remove_tail` 操作中,我们需要判断目标节点是否已经在链表的头部或尾部,以避免对空指针进行操作。此外,在每次操作中,我们还需要更新哈希表中对应节点的指针。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值