设计LRU缓存

LRU缓存

  • LRU缓存的实现思路
  • LRU缓存的操作
  • C++11 STL实现LRU缓存
  • 自行设计双向链表 + 哈希表

LRU(Least Recently Used,最近最少使用)缓存是一种常见的缓存淘汰算法,其基本思想是:当缓存空间已满时,移除最近最少使用的数据。LRU缓存通常通过链表(双向链表)和哈希表相结合来实现,利用哈希表快速查找,链表保持数据的使用顺序。

链接:leetcode 设计LRU缓存

LRU缓存的实现思路

实现思路:哈希表 + 双向链表

  • 为什么使用哈希表?
    哈希表:用来存储键值对,可以在常数时间内(O(1))进行查找、插入和删除操作。

  • 为什么使用双向带头尾链表?
    双向链表:用来维护数据的使用顺序。最近使用的元素放在链表的头部,最久未使用的元素放在链表的尾部。通过这种方式可以在O(1)的时间复杂度下实现删除最久未使用的元素。

LRU缓存的操作

  • Get(key): 如果键存在于缓存中,返回对应的值并将该键值对移到链表头部,表示最近被访问过。如果不存在,返回-1。
  • Put(key, value): 插入键值对。如果缓存已满,则删除最久未使用的元素,之后插入新的键值对,并将其移到链表头部。

C++11 STL实现LRU缓存

时间复杂度分析:
get(key):查找操作是O(1),然后通过 touch 函数将键移到链表头部,也是在O(1)时间内完成的。
put(key, value):插入或更新键值对的操作是O(1),如果缓存满了需要删除最久未使用的元素(evict),删除操作也是O(1)。因此,get 和 put 操作的时间复杂度都是 O(1)。

#include <iostream>
#include <unordered_map>
#include <list>

class LRUCache {
public:
    LRUCache(int capacity) : capacity(capacity) {}

    // 获取缓存中的值
    int get(int key) {
        auto it = cache.find(key);
        if (it == cache.end()) {
            return -1;  // 未找到键,返回 -1
        }
        touch(it);  // 标记为最近使用
        return it->second.first;  // 返回对应的值
    }

    // 插入新的键值对
    void put(int key, int value) {
        auto it = cache.find(key);
        if (it != cache.end()) {
            touch(it);  // 标记为最近使用
            it->second.first = value;  // 更新值
        } else {
            if (cache.size() == capacity) {
                evict();  // 删除最久未使用的元素
            }
            // 插入新的键值对到链表头部
            order.push_front(key);
            cache[key] = {value, order.begin()};  // 存入哈希表,值和链表位置
        }
    }

private:
    // 更新元素为最近使用
    void touch(std::unordered_map<int, std::pair<int, std::list<int>::iterator>>::iterator it) {
        int key = it->first;
        order.erase(it->second.second);  // 删除当前元素
        order.push_front(key);  // 将元素插入链表头部
        it->second.second = order.begin();  // 更新迭代器位置
    }

    // 淘汰最久未使用的元素
    void evict() {
        int key_to_evict = order.back();  // 获取尾部元素(最久未使用)
        order.pop_back();  // 从链表中移除
        cache.erase(key_to_evict);  // 从哈希表中删除
    }

    int capacity;  // 缓存容量
    std::list<int> order;  // 双向链表,维护键的访问顺序
    std::unordered_map<int, std::pair<int, std::list<int>::iterator>> cache;  // 哈希表,存储键值对和链表位置
};

int main() {
    LRUCache cache(2);  // 设置缓存容量为2
    cache.put(1, 1);    // 缓存: {1=1}
    cache.put(2, 2);    // 缓存: {1=1, 2=2}
    std::cout << cache.get(1) << std::endl;  // 返回 1,缓存: {2=2, 1=1}
    cache.put(3, 3);    // 淘汰键 2,缓存: {1=1, 3=3}
    std::cout << cache.get(2) << std::endl;  // 返回 -1 (未找到)
    cache.put(4, 4);    // 淘汰键 1,缓存: {3=3, 4=4}
    std::cout << cache.get(1) << std::endl;  // 返回 -1 (未找到)
    std::cout << cache.get(3) << std::endl;  // 返回 3
    std::cout << cache.get(4) << std::endl;  // 返回 4

    return 0;
}

效果:代码简洁,但效率不高。
在这里插入图片描述

自行设计双向链表 + 哈希表

#include <iostream>
#include <unordered_map>

using namespace std;

struct DLinkedNode {
    int key, value;
    DLinkedNode* prev;
    DLinkedNode* next;
    DLinkedNode() : key(0), value(0), prev(nullptr), next(nullptr) {}
    DLinkedNode(int _key, int _value) : key(_key), value(_value), prev(nullptr), next(nullptr) {}
};

class LRUCache {
private:
    unordered_map<int, DLinkedNode*> cache;
    DLinkedNode* head;
    DLinkedNode* tail;
    int size;
    int capacity;

public:
    LRUCache(int _capacity) : capacity(_capacity), size(0) {
        // 使用伪头部和伪尾部节点
        head = new DLinkedNode();
        tail = new DLinkedNode();
        head->next = tail;
        tail->prev = head;
    }
    
    int get(int key) {
        if (!cache.count(key)) {
            return -1;  // 如果找不到该键,返回 -1
        }
        DLinkedNode* node = cache[key];
        moveToHead(node);  // 移动到链表头部
        return node->value;  // 返回值
    }
    
    void put(int key, int value) {
        if (!cache.count(key)) {
            // 如果 key 不存在,创建新节点
            DLinkedNode* node = new DLinkedNode(key, value);
            cache[key] = node;
            addToHead(node);  // 添加到链表头部
            ++size;
            if (size > capacity) {
                // 超过容量,删除尾部节点
                DLinkedNode* removed = removeTail();
                cache.erase(removed->key);  // 从哈希表中删除该键
                delete removed;  // 防止内存泄漏
                --size;
            }
        }
        else {
            // 如果 key 存在,更新值,并移到头部
            DLinkedNode* node = cache[key];
            node->value = value;
            moveToHead(node);
        }
    }

    void addToHead(DLinkedNode* node) {
        node->prev = head;
        node->next = head->next;
        head->next->prev = node;
        head->next = node;
    }
    
    void removeNode(DLinkedNode* node) {
        node->prev->next = node->next;
        node->next->prev = node->prev;
    }

    void moveToHead(DLinkedNode* node) {
        removeNode(node);  // 移除节点
        addToHead(node);   // 重新添加到头部
    }

    DLinkedNode* removeTail() {
        DLinkedNode* node = tail->prev;
        removeNode(node);
        return node;
    }
};

int main() {
    LRUCache cache(2);  // 缓存容量为2
    cache.put(1, 1);    // 缓存: {1=1}
    cache.put(2, 2);    // 缓存: {1=1, 2=2}
    cout << cache.get(1) << endl;  // 返回 1,缓存: {2=2, 1=1}
    cache.put(3, 3);    // 淘汰键 2,缓存: {1=1, 3=3}
    cout << cache.get(2) << endl;  // 返回 -1,键 2 不存在
    cache.put(4, 4);    // 淘汰键 1,缓存: {3=3, 4=4}
    cout << cache.get(1) << endl;  // 返回 -1,键 1 不存在
    cout << cache.get(3) << endl;  // 返回 3
    cout << cache.get(4) << endl;  // 返回 4

    return 0;
}

代码转自力扣官方题解。
效果:时间复杂度明显降低, 效率提高。在这里插入图片描述

总结

上述实现利用了哈希表和双向链表的组合,保证了LRU缓存操作的高效性。哈希表提供了O(1)的查找和更新时间,而双向链表提供了O(1)的插入和删除操作,确保了缓存的高效管理。这个实现适用于高性能缓存系统,如数据库缓存、Web缓存等。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/922866.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

跨平台应用开发框架(1)----Qt(组件篇)

目录 1.Qt 1.Qt 的主要特点 2.Qt的使用场景 3.Qt的版本 2.QtSDK 1.Qt SDK 的组成部分 2.安装 Qt SDK 3.Qt SDK 的优势 3.Qt初识 1.快速上手 widget.cpp mian.cpp widget.h Helloworld.pro 2.对象树 3.坐标系 4.信号和槽 1. 信号和槽的基本概念 2. 信号和槽的…

Vue3+SpringBoot3+Sa-Token+Redis+mysql8通用权限系统

sa-token支持分布式token 前后端代码&#xff0c;地球号: bright12389

专题二十三_动态规划_回文串系列问题_算法专题详细总结

目录 动态规划 回文串系列问题 1. 回⽂⼦串&#xff08;medium&#xff09; 解析&#xff1a; 解决回文串问题&#xff0c;这里提供三个思路&#xff1a; 1.中心扩展法&#xff1a;n^2 / 1 2.马拉车算法&#xff1a;n / n 3.动态规划算法&#xff1a;n^2 / n^2 1.状态表…

ES实用面试题

一、es是什么&#xff0c;为什么要用它&#xff1f; ES通常是Elasticsearch的简称&#xff0c;它是一个基于Lucene构建的开源搜索引擎。Elasticsearch以其分布式、高扩展性和实时数据分析能力而闻名&#xff0c;广泛用于全文搜索、日志分析、实时监控等多种场景。 基本特点&am…

实现在两台宿主机下的docker container 中实现多机器通讯

基于我的实验背景 上位机&#xff1a;ubuntu 20.04 (docker humble 22.04) 下位机&#xff1a;ubuntu 22.04&#xff08;docker noetic 20.04&#xff09; 目标&#xff1a;实现在上位机中的docker container 容器的22.04环境去成功远程访问 非同网段的下位机的20.04的contai…

FakeLocation Linux | Windows关于使用教程一些规范说明

前言:使用教程&#xff08;FakeLocation版本请使用1.2.xxx&#xff09;| (1.3.xxx 未测试) 环境模块&#xff0c;是指代FakeLocation开启以后会把环境弄的异常,环境模块可以保证环境安全Dia 作为软件需要在Lsp框架里面勾选激活使用&#xff0c;并且开启增强模式FakeLocation 请…

指针的奥秘:深入探索内存的秘密

前言 在计算机编程的广阔天地中&#xff0c;指针作为一种独特的数据类型&#xff0c;它不仅是C语言的核心&#xff0c;也是理解计算机内存管理的基石。指针的概念虽然强大&#xff0c;但对于初学者来说&#xff0c;它常常是学习过程中的一个难点。本文旨在揭开指针的神秘面纱&a…

Mairadb 最大连接数、当前连接数 查询

目录 查询数据库 最大连接数 查询当前连接总数 环境 Mariadb 10.11.6 跳转mysql数据库&#xff1a; 查询数据库 最大连接数 show variables like max_connections; 注意; 这个版本不能使用 &#xff1a; show variables like ‘%max_connections%’; 会报错 &#xff…

电影风格城市夜景旅拍Lr调色教程,手机滤镜PS+Lightroom预设下载!

调色教程 电影风格城市夜景旅拍通过 Lightroom 调色&#xff0c;将城市夜晚的景色打造出如同电影画面般的质感和氛围。以独特的色彩和光影处理&#xff0c;展现出城市夜景的魅力与神秘。 预设信息 调色风格&#xff1a;电影风格预设适合类型&#xff1a;人像&#xff0c;街拍…

代码管理之Gitlab

文章目录 Git基础概述场景本地修改未提交&#xff0c;拉取远程代码修改提交本地&#xff0c;远程已有新提交 GitIDEA引入Git拉取仓库代码最后位置 Git基础 概述 workspace 工作区&#xff1a;本地电脑上看到的目录&#xff1b; repository 本地仓库&#xff1a;就是工作区中隐…

【FPGA】Verilog:利用 4 个串行输入- 串行输出的 D 触发器实现 Shift_register

0x00 什么是寄存器 寄存器(Register)是顺序逻辑电路中使用的基本组成部分之一。寄存器用于在数字系统中存储和处理数据。寄存器通常由位(bit)构成,每个位可以存储一个0或1的值。通过寄存器,可以设计出计数器、加法器等各种数据处理电路。 0x01 寄存器的种类 基于 D 触发…

HTML实现 扫雷游戏

前言&#xff1a; 游戏起源与发展 扫雷游戏的雏形可追溯到 1973 年的 “方块&#xff08;cube&#xff09;” 游戏&#xff0c;后经改编出现了 “rlogic” 游戏&#xff0c;玩家需为指挥中心探出安全路线避开地雷。在此基础上&#xff0c;开发者汤姆・安德森编写出了扫雷游戏的…

微信小程序+Vant-自定义选择器组件(单选带筛选

实现效果 筛选是filter&#xff0c;搜索框如有显隐需要&#xff0c;需自行添加配置显隐参数弹出层高度样式需要手动修改&#xff0c;需自行添加配置高度参数.json文件配置"component": true, 实现代码 组件代码 <van-popup show"{{ show }}" posit…

【Linux课程学习】:环境变量:HOME,su与su - 的区别,让程序在哪些用户下能运行的原理,环境变量具有全局性的原因?

&#x1f381;个人主页&#xff1a;我们的五年 &#x1f50d;系列专栏&#xff1a;Linux课程学习 &#x1f337;追光的人&#xff0c;终会万丈光芒 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 目录 HOME环境变量&#xff1a; PWD环境变量&#…

Java基础 设计模式——针对实习面试

目录 Java基础 设计模式单例模式工厂模式观察者模式策略模式装饰器模式其他设计模式 Java基础 设计模式 单例模式 单例模式&#xff08;Singleton Pattern&#xff09; 定义&#xff1a;确保一个类只有一个实例&#xff0c;并提供一个全局访问点来访问这个实例。适用场景&…

QRCode.toDataURL() vue3 uniapp h5在 Android环境下二维码显示不出来

“qrcode”: “^1.5.4” 修改前&#xff08;在浏览器里面是可以加载的&#xff09;&#xff1a; 查资料好像是Android上加载的是canvas&#xff0c;不是加载的img。 修改后&#xff1a; 这里val其实打印出来是svg代码&#xff0c;所以用v-html就好了。

数据结构——排序算法第一幕(插入排序:直接插入排序、希尔排序 选择排序:直接选择排序,堆排序)超详细!!!!

文章目录 前言一、排序1.1 概念1.2 常见的排序算法 二、插入排序2.1 直接插入排序2.2 希尔排序希尔排序的时间复杂度 三、选择排序3.1 直接选择排序3.2 堆排序 总结 前言 时间很快&#xff0c;转眼间已经到数据结构的排序算法部分啦 今天我们来学习排序算法当中的 插入排序 和 …

第32周:猴痘病识别(Tensorflow实战第四周)

目录 前言 一、前期工作 1.1 设置GPU 1.2 导入数据 1.3 查看数据 二、数据预处理 2.1 加载数据 2.2 可视化数据 2.3 再次检查数据 2.4 配置数据集 2.4.1 基本概念介绍 2.4.2.代码完成 三、构建CNN网络 四、编译 五、训练模型 六、模型评估 6.1 Loss和Accuracy…

【创建型设计模式】工厂模式

【创建型设计模式】工厂模式 创建型设计模式第二期&#xff01;本期介绍简单工厂模式和工厂方法模式。 简单工厂模式 简单工厂模式&#xff08;又叫作静态工厂方法模式&#xff09;&#xff0c;其属于创建型设计模式&#xff0c;简单工厂模式不属于设计模式中的 23 种经典模…

【Linux】安装cuda

一、安装nvidia驱动 # 添加nvidia驱动ppa库 sudo add-apt-repository ppa:graphics-drivers/ppa sudo apt update# 查找推荐版本 sudo ubuntu-drivers devices# 安装推荐版本 sudo apt install nvidia-driver-560# 检验nvidia驱动是否安装 nvidia-smi 二、安装cudatoolkit&…