【算法】LRU算法

news2024/11/20 14:39:38

LRU算法

LRU(Least Recently Used) 即最近最少使用,属于典型的内存淘汰机制。

根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”,其思路如下图所示:

img

该算法需要达到两个目的:①可以轻易的更新最新的访问数据。②轻易的找出最近最少未使用的数据。所以要用到哈希表+双向链表实现。利用map,获取key对应的value是O(1),利用双向链表,实现新增和删除都是O(1)。

传统意义的LRU算法是为每一个Cache对象设置一个计数器,每次Cache命中则给计数器+1,而Cache用完,需要淘汰旧内容,放置新内容时,就查看所有的计数器,并将最少使用的内容替换掉。它的弊端很明显,如果Cache的数量少,问题不会很大, 但是如果Cache的空间过大,达到10W或者100W以上,一旦需要淘汰,则需要遍历所有计数器,其性能与资源消耗是巨大的。效率也就非常的慢了。双链表LRU的原理: 将Cache的所有位置都用双链表连接起来,当一个位置被命中之后,就将通过调整链表的指向,将该位置调整到链表头的位置,新加入的Cache直接加到链表头中。 这样,在多次进行Cache操作后,最近被命中的,就会被向链表头方向移动,而没有命中的,则向链表后面移动,链表尾则表示最近最少使用的Cache。当需要替换内容时候,链表的最后位置就是最少被命中的位置,我们只需要淘汰链表最后的部分即可。

LRU数据结构如下图:

HashLinkedList

根据上图我们可以分析一下:

  1. 如果我们每次默认从链表尾部添加元素,那么显然越靠尾部的元素就是最近使用的,越靠头部的元素就是最久未使用的。
  2. 对于某一个 key,我们可以通过哈希表快速定位到链表中的节点,从而取得对应 val
  3. 链表显然是支持在任意位置快速插入和删除的,改改指针就行。只不过传统的链表无法按照索引快速访问某一个位置的元素,而这里借助哈希表,可以通过 key 快速映射到任意一个链表节点,然后进行插入和删除。
  • 版本1:自己实现循环链表存储,没有用API

    /********************不用API的版本*************************/
    /********************简单说一下思路*************************/
    //1.首先hash表用的是unordered_map来实现,用来查找key对应的node节点,所以hash表应该是[key,node]形式存储
    //2.LRUCache这个类实现双向链表的添加,删除,更新和遍历
    //3.同时这个类还要实现get和put两个功能
    //4.我这里用的是循环双向链表,因此查找链表尾端的元素为O(1),正常的双向链表是O(n)
    //总结:最重要的就是hash表中的key对应的不是int而是一个node节点,这个要记住
    #include<unordered_map>
    #include<iostream>
    struct Node{
        int key;
        int value;
        Node* pre;
        Node* next;
        Node(){}
        Node(int k, int v):key(k), value(v), pre(nullptr), next(nullptr){}
    };
    
    class LRUCache{
    private:
        //通过key可以找到位于链表中的节点
        std::unordered_map<int, Node*> hash;
        int capacity;
        Node* head_node;
    public:
        LRUCache(int cap){
            capacity = cap;
            head_node = new Node();
            //初始化dummy_Node,next和pre都指向自己
            head_node->next = head_node->pre = head_node;
        }
        //将新来的插入双向链表头部
        void add_Node(Node* n);
        //将某个节点拿出来重新插入头部
        void update_Node(Node* n);
        //移除链表中最后一个(最久未使用)
        void pop_back();
        //输出LRU结构
        void show();
        int get(int key);
        void put(int key, int value);
    };
    
    //注意,该节点可能是新节点,也可能是已经存在的有重新入链表的节点
    void LRUCache::add_Node(Node* n){
        //表示当前节点n就是dummy的next节点,不用加入
        if(n->pre == head_node){
            return;
        }
        //将节点n插入head_node后面
        n->pre = head_node;
        n->next = head_node->next;
        head_node->next->pre = n;
        head_node->next = n;
    }
    
    void LRUCache::update_Node(Node* n){
        //表示当前节点n就是dummy的next节点,不用断掉
        if(n->pre == head_node){
            return;
        }
        n->next->pre = n->pre;
        n->pre->next = n->next;
        add_Node(n);
    }
    
    //弹出链表的最后一个,由于是循环链表,就是head_node->pre
    void LRUCache::pop_back(){
        Node* tmp = head_node->pre;
        head_node->pre = tmp->pre;
        tmp->pre->next = head_node;
        //删除unordered_map中的key
        hash.erase(tmp->key);
    }
    
    void LRUCache::show(){
        //链表中没有节点,退出
        if(head_node->next = head_node){
            return;
        }
        Node* tmp = head_node->next;
        while(tmp->next != head_node){
            std::cout<<"key:"<<tmp->key<<",vlaue:"<<tmp->value<<std::endl;
        }
    }
    int LRUCache::get(int key){
        auto it = hash.find(key);
        if(it == hash.end()){
            std::cout<<"there is no key"<<std::endl;
            return -1;
        }
        //取出key对应的node节点
        Node* node = it->second;
        update_Node(node);
        return node->value;
    
    }
    void LRUCache::put(int key, int value){
        auto it = hash.find(key);
        if(it == hash.end()){
            Node* node = new Node(key, value);
            add_Node(node);
            hash.insert({key, node});
            if(hash.size() > capacity){
                
                pop_back();
            }
        }else{
            it->second->value = value;
            update_Node(it->second);
        }
    }
    
  • 版本2:使用deque,为什么使用deque说的很清楚

    /****************注意unordered_map的插入************/
    
    #include <iostream>
    #include <deque>
    #include <unordered_map>
    #include <list>
    
    class LRUCache{
    private:
        int capacity;
        //1.之所以用deque不用list是因为移除尾部元素的时候,deque方便
        //2.deque里面可以存储自定的node类型,也可以用pair表示,这里我用pair了
        std::deque<std::pair<int, int>> my_deque;
        //通过key找到对应key在deque中的位置
        std::unordered_map<int, std::deque<std::pair<int, int>>::iterator> hash;
    public:
        LRUCache(int cap):capacity(cap){}
        int get(int key);
        void put(int key, int value);
    };
    
    int LRUCache::get(int key){
        if(hash.find(key) == hash.end()){
            std::cout<<"there is no key"<<std::endl;
            return -1;
        }
        std::pair<int, int> tmp = *hash[key];
        my_deque.erase(hash[key]);
        my_deque.push_front(tmp);
        //更新hash表中对应key位于deque的位置
        hash[key] = my_deque.begin();
        return tmp.second;
    }
    
    void LRUCache::put(int key, int value){
        if(hash.find(key) == hash.end()){
            if(my_deque.size() >= capacity){
                //把hash表中的抹除,然后删除deque中的
                auto it = my_deque.back();
                hash.erase(it.first);
                my_deque.pop_back();
                my_deque.push_front({key, value});
                hash.insert({key, my_deque.begin()});
            }else{
                my_deque.push_front({key, value});
                hash.insert({key, my_deque.begin()});
            }
        }else{
            //更新就行
            my_deque.erase(hash[key]);
            my_deque.push_front({key, value});
            //更新hash表中key的位置
            hash[key] = my_deque.begin();
        }
    }
    

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

了解ASP.NET Core 中的文件提供程序

写在前面 ASP.NET Core 通过文件提供程序来抽象化文件系统访问。分为物理文件提供程序(PhysicalFileProvider)和清单嵌入的文件提供程序(ManifestEmbeddedFileProvider)还有复合文件提供程序(CompositeFileProvider )&#xff1b;其中PhysicalFileProvider 提供对物理文件系统…

PPT插件-大珩助手-选择同类

选择同类-颜色 对于选中的形状&#xff0c;一键选中当前页中的所有相同颜色的形状 选择同类-文本 一键选择当前页中的所有文本对象 选择同类-非文本 一键选择当前页中的所有非文本对象 选择同类-反选 一键选择当前页未选择的对象 软件介绍 PPT大珩助手是一款全新设计的…

【读书笔记】《白帽子讲web安全》浏览器安全

目录 第二篇 客户端脚本安全 第2章 浏览器安全 2.1同源策略 2.2浏览器沙箱 2.3恶意网址拦截 2.4高速发展的浏览器安全 第二篇 客户端脚本安全 第2章 浏览器安全 近年来随着互联网的发展&#xff0c;人们发现浏览器才是互联网最大的入口&#xff0c;绝大多数用户使用互联…

【办公类-19-01】20240108图书统计登记表制作(23个班级)EXCEL复制表格并合并表格

背景需求&#xff1a; 制作一个EXCEL模板&#xff0c;每个班级的班主任统计 班级图书量&#xff08;一个孩子10本&#xff0c;最多35个孩子350本&#xff09; EXCEL模板 1.0版本&#xff1a; 将这个模板制作N份——每班一份 项目:班级图书统计表 核心:一个EXCEL模板批量生成…

合宙海外模组硬核出击,Air780UAAir780UU全新上市

简介 随着国内市场竞争日趋激烈&#xff0c;企业产品出海已呈如火如荼之势&#xff0c;向外发展拼商机更需硬核优势。 合宙作为物联网行业的核心器件提供商&#xff0c;将逐步推出系列高性价比海外模组&#xff0c;全面助力行业客户出海。现针对亚太、欧洲地区&#xff0c;全…

ChatGPT知名开源项目有哪些

ChatGPT-Next-Web&#xff1a;基于ChatGPT API的私有化部署网页聊天系统 主要功能&#xff1a; 只需在 1 分钟内即可在 Vercel 上一键免费部署&#xff0c;支持私有服务器快速部署&#xff0c;支持使用私有域名支持ChatGPT3.5、4等常见模型Linux/Windows/MacOS 上的紧凑型客户…

【Java】知识——各类编码格式以及样例

一、 #ASCII 码 计算机内所有的信息都是二进制位。一个字节包含 8 个二进制位&#xff0c;可以表示 256 个状态&#xff0c;每个状态表示一个符号。 ASCII 码一共规定了128个字符的编码&#xff0c;比如空格 SPACE 是32&#xff08;二进制00100000&#xff09;&#xff0c;大写…

Shopee买家通系统:轻松获取虾皮买手号的智能利器

近来&#xff0c;有一款强大的软件引起了广泛关注&#xff0c;它就是Shopee买家通系统&#xff0c;为用户提供了自动化注册虾皮买手号的便捷途径。目前&#xff0c;该软件已覆盖菲律宾、泰国、马来西亚、越南、巴西、印度尼西亚等多个国家&#xff0c;为用户提供更广泛的服务。…

CUTANA™ pAG-Tn5 for CUTTag

CUTANA pAG-Tn5是靶向剪切及转座酶(CUT&Tag)技术中进行高效绘制染色质特征的关键试剂。与ChIP-seq相比&#xff0c;CUT&Tag在降低细胞需求量和测序深度的信噪比方面进行了显著改进。CUTANA pAG-Tn5是一种高活性的E. coli转座酶突变体(Tn5)与蛋白A/G的融合产物&#xff…

龍运当头--html做一个中国火龙祝大家龙年大吉

🐉效果展示 🐉HTML展示 <body> <!-- partial:index.partial.html --> <svg><defs><g id=

怎么选择数据安全交换系统,能够防止内部员工泄露数据?

数据泄露可能给企业带来诸多风险&#xff1a;财产损失、身份盗窃、骚扰和诈骗、经济利益受损、客户信任度下降、法律风险和责任等&#xff0c;《2021年度数据泄漏态势分析报告》中显示&#xff0c;在数据泄露的主体中&#xff0c;内部人员导致的数据泄漏事件占比接近60%。 员工…

ECMAScript6历史-前端开发+ECMAScript+基础语法+入门教程

ECMAScript6详解 ECMAScript 历史 我们首先来看 ECMA 是什么。ECMA&#xff0c;读音类似“埃科妈”&#xff0c;是欧洲计算机制造商协会&#xff08;European Computer Manufacturers Association&#xff09;的简称&#xff0c;是一家国际性会员制度的信息和电信标准组织。19…

FCRP第一题详解一

先看效果: 20240106-094943 看他的第一要求: 1.整个模板只能出现一个数据集,下拉复选框与报表主体共用一个数据集,且不影响互相显示。 所以这里不能通过SQL语句来过滤,SQL语句中中只能全部查询出来,这样保证了下拉框的数据是全部,单元格里的数据只能通过数据列过滤来实…

回归预测 | Matlab实现DE-BP差分算法优化BP神经网络多变量回归预测

回归预测 | Matlab实现DE-BP差分算法优化BP神经网络多变量回归预测 目录 回归预测 | Matlab实现DE-BP差分算法优化BP神经网络多变量回归预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab实现DE-BP差分算法优化BP神经网络多变量回归预测&#xff08;完整源码和…

openssl3.2 - 自己构建openssl.exe的VS工程(在编译完的源码版本上)

文章目录 openssl3.2 - 自己构建openssl.exe的VS工程(在编译完的源码版本上)概述笔记备注END openssl3.2 - 自己构建openssl.exe的VS工程(在编译完的源码版本上) 概述 将openssl3.2编译出来了(openssl3.2 - 编译) 安装后的openssl.exe可以干openssl3.2所有的事情, 用openssl.…

Java缓冲字符流

PrintWriter的自动行刷新功能 如果实例化PW时第一个参数传入的是一个流&#xff0c;则此时可以再传入一个boolean型的参数&#xff0c;此值为true时就打开了自动行刷新功能。 即: 每当我们用PW的println方法写出一行字符串后会自动flush. package io; ​ import java.io.*; i…

[Docker] Mac M1系列芯片上完美运行Docker

docker pull qinchz/dm8-arm64 container_name: dm8ports:- "5236:5236"mem_limit: 1gmemswap_limit: 1gvolumes:- /data/dm8:/home/dmdba/data 数据库实例参数已修改&#xff0c;接近oracle使用习惯 #字符集 utf-8 CHARSET1 #VARCHAR 类型对象的长度以字符为单位 …

工程监测领域振弦采集仪的数据处理与分析方法探讨

工程监测领域振弦采集仪的数据处理与分析方法探讨 在工程监测领域&#xff0c;振弦采集仪是常用的一种设备&#xff0c;用于测量和记录结构物的振动数据。数据处理和分析是使用振弦采集仪得到的数据的重要环节&#xff0c;可以帮助工程师了解结构物的振动特性&#xff0c;评估…

C++信息学奥赛1087:级数求和

#include<bits/stdc.h> using namespace std; int main() {int k;cin>>k; // 从标准输入获取一个整数kdouble sum0; // 初始化sum变量为0&#xff0c;用于存储累加的和int i; // 初始化循环变量ifor(i1;;i){ // 无限循环&#xff0c;直到满足条件时跳出循环sum(dou…