LRU缓存机制-leetcode

问题

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

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

进阶:

你是否可以在 O(1) 时间复杂度内完成这两种操作?

这个问题是leetcode的第146题,同时也是我2020.4.24号面试网易游戏的时候的一道面试题。

首先学习一下STL容器list的使用
List封装了链表。常用操作有:
assign() 给list赋值
back() 返回最后一个元素
begin() 返回指向第一个元素的迭代器
clear() 删除所有元素
empty() 如果list是空的则返回true
end() 返回末尾的迭代器
erase() 删除一个元素
front() 返回第一个元素
get_allocator() 返回list的配置器
insert() 插入一个元素到list中
max_size() 返回list能容纳的最大元素数量
merge() 合并两个list
pop_back() 删除最后一个元素
pop_front() 删除第一个元素
push_back() 在list的末尾添加一个元素
push_front() 在list的头部添加一个元素
rbegin() 返回指向第一个元素的逆向迭代器
remove() 从list删除元素
remove_if() 按指定条件删除元素
rend() 指向list末尾的逆向迭代器
resize() 改变list的大小
reverse() 把list的元素倒转
size() 返回list中的元素个数
sort() 给list排序
splice() 合并两个list
swap() 交换两个list
unique() 删除list中重复的元素

下面看具体代码:

#include <iostream>
#include <list>
#include <numeric>
#include <algorithm>
using namespace std;

list<int> ListInt;
list<int> ListChar;

int main(int argc, char const *argv[])
{
    ListInt.push_front(2);
    ListInt.push_front(1);

    ListInt.push_back(3);
    ListInt.push_back(4);

    cout << "listint.begin()----listint.end():" << endl;
    for (list<int>::iterator iter = ListInt.begin();iter != ListInt.end();iter++) {
        cout << *iter << " ";
    }
    cout << endl;

    cout << "listint.rbegin()----listint.rend():" <<endl;
    for (list<int>::reverse_iterator iter = ListInt.rbegin();iter != ListInt.rend();iter++) {
        cout << *iter << " ";
    }
    cout << endl;

    int result = accumulate(ListInt.begin(),ListInt.end(),0);
    cout << "Sum=" << result << endl;
    cout <<"------------------"<<endl;

    ListChar.push_front('A');
    ListChar.push_front('B');
    ListChar.push_back('x');
    ListChar.push_back('y');

    cout << "listchar.begin()---listchar.end(): " << endl;
    for (list<int>::iterator iter = ListChar.begin();iter != ListChar.end();iter++) {
        cout << char(*iter) << " "; 
    }
    cout << endl;

    list<int>::iterator m = max_element(ListChar.begin(),ListChar.end());
    cout << "The maximum element in listchar is : " << char(*m) << endl;

    return 0;
}

上述代码的运行结果如下:
在这里插入图片描述
看下一段代码,关于list的更多的用法:

#include <iostream>
#include <list>

using namespace std;

void put_list(list<int> l,string name) {
    list<int>::iterator plist;
    cout << "The contents of " << name << ":";
    for (plist = l.begin();plist != l.end();plist++) {
        cout << *plist << " ";
    }
    cout << endl;
}

int main(int argc, char const *argv[])
{
    list<int> l1;
    list<int> l2(10,6);  // 初始化为10个6
    list<int> l3(l2.begin(),--l2.end()); // 初始化为9个6 

    list<int>::iterator iter;
    put_list(l1,"list1");
    put_list(l2,"list2");
    put_list(l3,"list3");

    l1.push_back(2);
    l1.push_back(4);

    cout << "l1.push_back(2) and l1.push_back(4): " <<endl;

    put_list(l1,"list1");

    l1.insert(++l1.begin(),3,9); // 在2和4之间插入3个9
    cout << "l1.insert(++l1.begin(),3,9):" << endl;

    put_list(l1,"list1");

    cout << "l1.front() = " << l1.front() << endl;
    cout << "l1.end() = " << l1.back() << endl;

    l1.pop_front();
    l1.pop_back();
    
    cout<<"l1.pop_front() and l1.pop_back():"<<endl;

    put_list(l1,"list1");

    //  清除l1中的第二个元素
    l1.erase(++l1.begin());
    cout<<"list1.erase(++list1.begin()):"<<endl;
    put_list(l1,"list1");

    // 对l2进行赋值并显示
    l2.assign(8,1); // 让l2变为8个1
    cout << "l2.assign(8,1): " << endl;

    put_list(l2,"list2");

    cout << "l1.max_size(): " << l1.max_size() << endl;
    cout << "l1.size(): " << l1.size() << endl;
    cout << "l1.empty(): " << l1.empty() << endl;

    put_list(l1,"list1");
    put_list(l3,"list3");
    cout << "list1 > list3: " << (l1 > l3) << endl;
    cout << "list1 < list3: " << (l1 < l3) << endl;

    l1.sort();
    put_list(l1,"list1");

    l1.splice(++l1.begin(),l3); // 合并。将l3放在了l1的第二个位置
    put_list(l1,"list1");
    put_list(l3,"list3");

    return 0;
}

运行结果如下:

在这里插入图片描述
下面开始LRU算法的实现。
LRU的关键是最近最少使用,就是在缓冲区满之后,如何找到这个最近最少使用,然后将其替换掉。
方法为:维护一个队列,每次替换队头的,同时每使用一次,就将其对应的数据放到队尾。
所以,我最先想到了如下代码实现:

class LRUCache {
public:
    int capacity;
    unordered_map<int,int> data; // 储存键值对数据
    list<int> que;   // 用来寻找最近最少使用的数据的key

    LRUCache(int capacity) {
        this->capacity = capacity;
    }
    
    int get(int key) {
        if (data.count(key)) {  // 如果找得到
            put(key,data[key]);  // 这句话的目的是完成对que链表的更新,因为get代表使用。
            return data[key]; // 返回对应的value值
        }
        return -1; // 否则返回-1
    }
    
    void put(int key, int value) {
        if (data.count(key)) {
            data[key] = value; // 如果找的到,对value值进行修改
            move_to_back(key); // 因为刚刚使用,所以将对应key值放到que的队尾
        }
        else if (data.size() < capacity) {
            data[key] = value;  // 找不到且容量够,不需要替换,则将该数据加入map
            que.push_back(key);  // 同时将key放到队尾
        }
        else {
            int dele = que.front(); // 因为容量已满,选择要删除的key,即队头
            que.pop_front();      // 弹出 
            unordered_map<int,int>::iterator del = data.find(dele);    // 找到要删除的key对应的迭代器
            data.erase(del); // 在map删掉对应键值对节点
            put(key,value);  // 此时容量未满,调用自身,完成put
        }
    }
    void move_to_back(int key) {  // 通过遍历,将对应key先删除,再push_back到队列尾
        for (list<int>::iterator iter = que.begin();iter != que.end();iter++) {
            if (*iter == key) {
                que.erase(iter);
                que.push_back(key);
                return;
            }
        }
    }
    void display() {  // 方便调试
        cout << "data: " << endl;
        for (unordered_map<int,int>::iterator iter = data.begin();iter != data.end();iter++) {
            cout << iter->first << " " << iter->second << endl;
        }
        cout << "list: " << endl;
        for (list<int>::iterator iter = que.begin();iter != que.end();iter++) {
            cout << *iter <<" " ;
        }
        cout << endl;
    }
};

此代码时间复杂度较高,为O(n),做不到O(1)。

对其进行优化,提出一种哈希链表的数据结构,将哈希表的查找速度与链表的有序性结合起来。
定义如下:

unordered_map<int,list<pair<int,int> >::iterator > data;
    list<pair<int,int> > que;

其哈希表的值为一个指向list的迭代器,然后list内部的数据类型保存了要查找的键值对。即哈希表通过key值找到一个指向list中一个个体的指针,然后通过这个指针可以找到对应的value。这样做的目的是即可以快速查找,也可以让数据按照我们定义的规则排序。

代码如下:

class LRUCache {
public:
    int capacity;
    unordered_map<int,list<pair<int,int> >::iterator > data;
    list<pair<int,int> > que;

    LRUCache(int capacity) {
        this->capacity = capacity;
    }
    
    int get(int key) {
        auto it = data.find(key); // 智能指针
        if (it == data.end())     //  如果找不到,返回-1
            return -1;
        int val = it->second->second;   // val要先通过key找到一个list<pair<int,int> >::iterator,然后这个迭代器会指向一个pair,这个pair的第二个元素为val。
        que.erase(it->second);  // 先在链表中删除
        que.push_back(make_pair(key,val)); // 再将其放在链表的尾部
        it->second = --que.end(); // 再次建立,哈希表与链表之间的关系。注意,que.end()并不指向最后一个元素,--que.end()才指向最后一个。
        return val;
    }
    
    void put(int key, int value) {
        auto it = data.find(key);
        if (it != data.end()) {
            que.erase(it->second); // 找得到,就先删掉
        }
       
        que.push_back(make_pair(key,value)); // 数据加到链表里
        data[key] = --que.end(); // 为新数据建议哈希表与链表之间的关系
        
        if (que.size() > capacity) {  // 若容量满
            int key = que.front().first;  // 找到链表首部的key
            data.erase(key); // 通过key值再hash表删除数据
            que.pop_front(); // 将链表首部的数据弹出
        }
    }
};

这样,这个算法的复杂度达到了O(1).

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值