LRU最少使用算法与LFU最不经常使用算法

前提

默默的打开了我的《计算机组成原理》崭新的课本,上个月老师网课讲的内容还是记忆犹新

可以先了解下计算机系统概述

冯诺依曼结构体系的构成有着这样五个部分

  • 运算器
  • 控制器
  • 存储器
  • 输入设备
  • 输出设备

其中运算器与控制器构成了CPU,输入设备与输出设备属于适配器
而存储器的要求就是,容量大,速度快,成本低

计算机中对于存储器需要满足的三个特点,就是容量大,速度快,成本低。但是在存储器的容量非常大的情况下,想要满足每次取数据的速度都很快,这是非常难的。

为了解决这个矛盾的问题,计算机系统采用了多级存储体系的结构,即使用高速缓冲存储器(cache),主存储器和外存储器。

  • CPU可以直接访问的存储器称为主存储器,包括主存储器和cache
  • CPU不能直接访问的存储器称为外存储去,外存储器的信息必须调入内存储器后才能为CPU进行处理

在这里插入图片描述

主存储器

简称主存,是计算机系统的主要存储器,用来存放计算机运行期间的大量程序和数据,主要是由MOS半导体存储器组成。

他可以和cache交换指令和数据。

外存储器

简称外存,他是大容量的辅助存储器。目前主要使用磁盘存储器,磁带存储器,光盘存储器。

外存的特点是存储容量大,位成本低,通常用来存放系统程序和大型数据文件及数据库

高速缓冲存储器

简称cache,他是计算机系统中的一个高速,小容量,半导体存储器

他的存在就是为了提高计算机的处理速度,利用了cache的高速存取指令和数据。和主存储器相比,他的存取速度快,但是存储容量小。
在这里插入图片描述
当处理器想要读取主存储器中的数据时,先检查这个数据是否在高速缓冲存储器中。

  • 如果在,则该字节从高速冲刷存储器传递给处理器;
  • 如果不在,然后才在主存储器中查找,然后再把该数据周围固定数目的字节组成一块主存储器数据,先被读入高速缓冲存储器中,然后该数据才从高速缓冲存储器传递处理器。

由于访问的局部性现象存在,所以当一块数据被取入高速缓冲存储器来满足一次存储器访问时,很可能之后会与多次访问的数据就是该数据周围的数据

cache的工作原理要求尽量保存最新的数据。当一个新的主存块需要拷贝到cache,而cache中运行存放的位置全部都被占满了,这时就需要发生替换

替换有三种算法

  1. 近期最少使用(LRU)算法
  2. 最不经常使用(LFU)算法
  3. 随机替换 。。。。。。。。

近期最少使用(LRU)算法

LRU算法将近期内长久没有被访问的数据替换出来。为每个数据设置一个计数器,cache每命中一次,命中的数据计数器清零,其他计数器+1。当需要替换的时候,就把计数器值最大的数据替换出来。

使用的方法:一个带头结点的双向循环链表

  • 每次插入数据,都在链表的头部插入,表示近期被使用
  • 查找数据的时候,把该数据移动到链表的头部,近期被使用
  • 需要替换的时候,就删除掉链表的尾部数据,他是近期使用次数最少的

在这里插入图片描述

问题来源

https://leetcode-cn.com/problems/lru-cache/
在这里插入图片描述

程序部分

struct node
{
    node* _pre;
    node* _next;
    int _key;
    int _value;

    node(int key,int value)
        :_key(key)
        ,_value(value)
        ,_pre(NULL)
        ,_next(NULL)
    {}
};

class LRUCache {
public:
    LRUCache(int capacity) {
        _capacity = capacity;
        head = new node(-1,-1);
        head->_next = head->_pre = head;
    }
    ~LRUCache()
    {
        delete head;
        _capacity = -1;
    }

    //从链表中移除该节点
    void RemoveNode(node* cur)
    {
        cur->_next->_pre = cur->_pre;
        cur->_pre->_next = cur->_next;
    }

    //头插
    void PushFront(node* cur)
    {
 
        head->_next->_pre = cur;
        cur->_next = head->_next;

        head->_next = cur;
        cur->_pre = head;
    }

    int get(int key) {
        if(map.find(key) != map.end())
        {
            node* cur = map[key];
            //移除该节点
            RemoveNode(cur);
            //把该节点放到头部
            PushFront(cur);
            return cur->_value;
        }
        return -1;
    }
    
    void put(int key, int value) {
        //如果存在key
        if(map.find(key) != map.end())
        {
            node* cur = map[key];
            //移除
            RemoveNode(cur);
            //头插
            PushFront(cur);
            cur->_value = value;
            return ;
        }

        //这里不存在key,value
        //判断内存是否满了
        if(map.size() == _capacity)
        {
            //移除队尾元素
            node* tail = head->_pre;
            RemoveNode(tail);
            map.erase(tail->_key);
            delete tail;
        }
        //队头插入key
        node* cur = new node(key,value);
        PushFront(cur);
        map[key] = cur;
    }
private:
    int _capacity;
    node* head;
    unordered_map<int,node*> map;
};

最不经常使用(LFU)算法

LFU算法认为,应将这段时间内访问次数最少的数据替换出。为此给每个数据设置一个计数器,每访问一次,计数器的值+1。当发送冲突的时候,就找到当前计数器的值最小的那一个数据,把这个数据替换成新的元素。

使用的方法:两个哈希表

  1. 第一个哈希表,可以利用key的值,快速找到是哪一个节点
  2. 第二个哈希表,利用一个访问的次数,快速找到当前访问次数的所有节点的链表,内部是一个STL中的list列表。
  3. 在同一个list列表中,列表的头部元素是最近访问的元素,列表的尾部是最久的访问元素。所以说插入元素的时候使用头插,删除的时候使用尾删

在这里插入图片描述

问题链接

https://leetcode-cn.com/problems/lfu-cache/submissions/
在这里插入图片描述

程序

struct node
{
    int _key;
    int _value;
    int _freq;//出现的频率

    node(int key,int value,int _freq)
        :_key(key)
        ,_value(value)
        ,_freq(_freq)
    {}
};

class LFUCache {
public:
    LFUCache(int capacity) {
        freq_map.clear();
        key_map.clear();
        _minfreq = 0;
        _capacity = capacity;
    }
    ~LFUCache()
    {
        freq_map.clear();
        key_map.clear();
        _capacity = _minfreq = -1;
    }

    int get(int key) {
        //如果容量为0 ,直接退出
        if(_capacity == 0) return -1;

        //如果当前值不存在,也直接退出
        if(key_map.find(key) == key_map.end()) return -1;

        list<node>::iterator cur = key_map[key];
        int val = cur->_value;
        int freq = cur->_freq;
 
        //移除key的节点,在freq的使用频率链表中
        freq_map[freq].erase(cur);

        //如果当前链表删除后为空
        if(freq_map[freq].size() == 0)
        {
            freq_map.erase(freq);
            if(_minfreq == freq) _minfreq++;
        }

        //插入key的点到 freq+1 的出现频率中,头插法
        freq_map[freq+1].push_front(node(key,val,freq+1));
        key_map[key] = freq_map[freq+1].begin();
        return val;
    }
    
    void put(int key, int value) {
        if(_capacity == 0) return ;
        //如果存在Key
        if(key_map.find(key) != key_map.end())
        {
            list<node>::iterator cur = key_map[key];
            int freq = cur->_freq;
            freq_map[freq].erase(cur);

            if(freq_map[freq].size() == 0)
            {
                freq_map.erase(freq);
                if(_minfreq == freq) _minfreq++;
            }
            freq_map[freq+1].push_front(node(key,value,freq+1));
            key_map[key] = freq_map[freq+1].begin();
            return ;
        }

        //此处是不存在key,需要插入的情况
        if(key_map.size() == _capacity)
        {
            //链表已经满了
            //通过当前的最小访问频率,拿到链表的尾部数据
            node tail = freq_map[_minfreq].back();
            key_map.erase(tail._key);
            freq_map[_minfreq].pop_back();

            if(freq_map[_minfreq].size() == 0) freq_map.erase(_minfreq);
        }

        _minfreq = 1;//更新当前最小访问次数
        freq_map[_minfreq].push_front(node(key,value,1));//新结点值访问了一次
        key_map[key] = freq_map[_minfreq].begin();
    }
private:
    unordered_map<int,list<node> > freq_map; //以出现的频率来建图
    unordered_map<int,list<node>::iterator> key_map; //通过key值找到这个节点的迭代器
    int _minfreq;  //当前最小使用次数
    int _capacity;//容量 
};
  • 7
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值