手撕LRU缓存

本文介绍了如何使用哈希表和双向链表实现LRU(最近最少使用)缓存,保证get和put操作的平均时间复杂度为O(1)。通过unordered_map存储键值对的迭代器,以及list进行键值对的物理存储,当缓存满时,删除最久未使用的项。get方法查找并更新键的位置,put方法插入或更新键值对,如果超出容量则删除最近最少使用的项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

请你设计并实现一个满足  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) 的平均时间复杂度运行。

要求复杂度达到O1,用哈希和双向链表

get方法,先看key存在不,不存在返回-1;存在了把他放到头部,返回结果

put方法,先看key存在不,存在,修改key对应的value,把key放到头部,这一步直接调get方便;如果不存在,看缓存满了没,满了就删除一直没用的,因此每次我们都把新用的放在了头部,因此没用的肯定就是最后一个,所以就删掉最后一个,把要新添加的加在头部

掌握list的splice方法

void splice (iterator position, list& x, iterator i);

简单来说就是把x这个listi处元素插入到position的位置 

下面是手撕代码:直接用了list,你也可以自己写一个双链表

class LRUCache {
    //双链表和哈希实现o1的插入删除查找
    //list和unordered_map底层是哈希表操作是o1
    //list记录kv,map映射k和list迭代器实习o1的访问list元素
public:
    int size;
    list<pair<int,int>>li;
    unordered_map<int,list<pair<int,int>>::iterator>map;
    LRUCache(int capacity):size(capacity) {
    }
    
    int get(int key) {
        if(map.find(key)==map.end())return -1;//没找到key
        else//key存在,把他放到头部,返回
        {
           /* auto it=*map[key];
            li.erase(map[key]);
            li.push_front(it);
            map[key]=li.begin();*///要知道list的splice,不然就得这么写,效率也不高
            li.splice(li.begin(),li,map[key]);
            return map[key]->second;
        }
    }
    
    void put(int key, int value) {
        if(get(key)!=-1){//key存在,因为调用了get,顺便也把他放到了头部
            map[key]->second=value;//只用修改map的信息就行
        }
        else{//key不存在
            if(size==li.size())//缓存满了,要删除不用的
            {
                map.erase(li.back().first);
                li.pop_back();
            }
            li.push_front({key,value}); //添加新的key
            map[key]=li.begin();
        }

    }
};

不借助stl,自己实现双向循环列表去使用

struct listnode
{
    int key;
    int val;
    listnode*next;
    listnode*pre;
   
    listnode(int k=-1,int v=-1):key(k),val(v),next(nullptr),pre(nullptr){};


};
class LRUCache {
private:
    listnode*vmhead;
    listnode*vmtail;
    int size=0;
    int cap;
    unordered_map<int,listnode*>mp;
public:
    LRUCache(int capacity):cap(capacity) {
        vmhead=new listnode();
        vmtail=new listnode();
        size=0;
        vmhead->next=vmtail;
        vmtail->pre=vmhead;
    }
    
    int get(int key) {
        if(mp.count(key))
        {
            //存在
            listnode*node=mp[key];
            removenode(node);
            return node->val;
        }
        //不存在
        return -1;
    }
    
    void put(int key, int value) {
        if(get(key)==-1)//不存在
        {
           
            listnode*node=new listnode(key,value);
            if(size>=cap){
                //满了
                deletetail();
            }
            //没满 
            ++size;
            inserthead(node);
            mp[key]=node;
        }
        else{
            //存在
            mp[key]->val=value;
        }
    }
    //头插
    void inserthead(listnode*node)
    {
        vmhead->next->pre=node;
        node->next=vmhead->next;
        vmhead->next=node;
        node->pre=vmhead;
    }

    //尾删除
    void deletetail()
    {
        mp.erase(vmtail->pre->key);
        deletenode(vmtail->pre);
    }
    //删结点
    void deletenode(listnode*cur)
    {
        cur->pre->next=cur->next;
        cur->next->pre=cur->pre;
    }
    //指定结点移动到头部
    void removenode(listnode*cur)
    {
        deletenode(cur);//删结点
        inserthead(cur);//头插
    }
};

/**
 * 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);
 */

### 动实现LRU缓存算法 LRU(Least Recently Used,最近最少使用)是一种常见的缓存淘汰策略。其实现的核心在于维护一个能够快速查找和更新的数据结构组合——哈希表(Hash Table)用于存储键值对并提供 O(1) 的查找时间,而双向链表(Doubly Linked List)则用来记录访问顺序以便于高效地移动节点到头部或将尾部节点移除。 以下是基于 Python 实现的一个简单版本的 LRU 缓存: #### 使用字典与双向链表的动实现 ```python class Node: def __init__(self, key, value): self.key = key self.value = value self.prev = None self.next = None class LRUCache: def __init__(self, capacity: int): self.capacity = capacity self.cache = {} # 哈希表用于映射键到节点 self.head = Node(0, 0) # 虚拟头结点 self.tail = Node(0, 0) # 虚拟尾结点 self.head.next = self.tail self.tail.prev = self.head def _add_node(self, node: Node): """将新节点添加到双向链表的头部""" node.prev = self.head node.next = self.head.next self.head.next.prev = node self.head.next = node def _remove_node(self, node: Node): """从双向链表中删除指定节点""" prev = node.prev next_ = node.next prev.next = next_ next_.prev = prev def _move_to_head(self, node: Node): """将某个已存在的节点移到链表头部""" self._remove_node(node) self._add_node(node) def _pop_tail(self) -> Node: """弹出链表中的最后一个实际节点(即最不常使用的节点)""" res = self.tail.prev self._remove_node(res) return res def get(self, key: int) -> int: """获取缓存中的值,如果存在,则将其标记为最近使用;否则返回 -1""" if key not in self.cache: return -1 node = self.cache[key] self._move_to_head(node) return node.value def put(self, key: int, value: int): """插入新的键值对,或者更新已有键的值,并调整其位置至最近使用""" if key in self.cache: node = self.cache[key] node.value = value self._move_to_head(node) else: new_node = Node(key, value) self._add_node(new_node) self.cache[key] = new_node if len(self.cache) > self.capacity: tail = self._pop_tail() del self.cache[tail.key] ``` 上述代码实现了 LRU 缓存的主要逻辑[^1]。`Node` 类表示双向链表中的单个节点,其中包含了 `key` 和 `value` 属性以及前后指针。`LRUCache` 利用了两个辅助方法 `_add_node` 和 `_remove_node` 来管理双向链表的操作,同时通过 `_move_to_head` 方法确保每次访问过的节点都被视为最近使用。 当容量超出限制时,会调用 `_pop_tail` 删除最久未使用的节点及其对应的哈希条目[^2]。 #### 使用 ES6 Map 的简化实现 另一种更简洁的方式可以利用 JavaScript 中的内置 `Map` 数据结构来完成相同的功能。由于 `Map` 默认保留了插入顺序,因此无需显式构建双向链表即可轻松模拟 LRU 行为[^3]: ```javascript class LRUCache { constructor(capacity) { this.capacity = capacity; this.map = new Map(); } get(key) { if (!this.map.has(key)) return -1; const value = this.map.get(key); this.map.delete(key); // 移除旧项 this.map.set(key, value); // 将该项重新加入以保持最新状态 return value; } put(key, value) { if (this.map.has(key)) { this.map.delete(key); // 如果已经存在该键,则先删除它 } this.map.set(key, value); if (this.map.size > this.capacity) { this.map.delete(this.map.keys().next().value); // 删除最早插入的一项 } } } ``` 此版本依赖于 `Map.prototype.keys()` 返回迭代器的能力自动跟踪最早的项目,从而减少了自定义链表的需求。 --- ###
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BearPot

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值