solution of LeetCode:146 LRU Cache

本文介绍了如何设计和实现LRU缓存机制,包括使用双向链表和哈希表作为数据结构的原因,以及如何通过这两种数据结构高效地完成get和set操作。文中详细解释了迭代器失效的问题,并给出了具体的实现代码。

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

LeetCode:LRU Cache

Design and implement a data structure for Least Recently Used (LRU) cache. It should support the following operations: get and set.

get(key) - Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1.
set(key, value) - Set or insert the value if the key is not already present. When the cache reached its capacity, it should invalidate the least recently used item before inserting a new item.

结题思路 :

题意要求我们利用LRU的规则模拟页面置换的过程。

最近最久未使用(LRU)的页面置换算法,是根据页面调入内存后的使用情况进行决策的。由于无法预测各页面将来的使用情况,只能利用“最近的过去”作为“最近的将来”的近似,因此,LRU置换算法是选择最近最久未使用的页面予以淘汰。

算法实现主要有两种方法:

  1. 计数器。最简单的情况是使每个页表项对应一个使用时间字段,并给CPU增加一个逻辑时钟或计数器。每次存储访问,该时钟都加1。每当访问一个页面时,时钟寄存器的内容就被复制到相应页表项的使用时间字段中。这样我们就可以始终保留着每个页面最后访问的“时间”。在置换页面时,选择该时间值最小的页面。
  2. 栈。用一个栈保留页号。每当访问一个页面时,就把它从栈中取出放在栈顶上。这样一来,栈顶总是放有目前使用最多的页,而栈底放着目前最少使用的页。

为了保持cache的性能,使查找,插入,删除都有较高的性能,我们使用双向链表(std::list)和哈希表(std::unordered_map)作为cache的数据结构:

  1. 双向链表插入删除效率高(单向链表中间节点需要提到头结点的前面时,无法再O(1)的时间复杂度内将中间节点的pre指向中间节点的next)
  2. 哈希表保存每个节点的地址,可以基本保证在O(1)时间内查找节点

查询或者访问节点时,如果节点存在,把该节点交换到链表头部
插入节点时,如果cache的size达到了上限,则删除尾部节点,同时要在hash表中删除对应的项。新节点都插入链表头部时,要在hash表中添加新节点的值和其对应的链表中的地址 。

这里需要注意的地方有两点:
1. 由于这里用了std::list,我们只能用迭代器来访问数据,所以hash中值的类型就应该是迭代器iterator类型。

迭代器失效的类型:

1.由于容器元素整体“迁移”导致存放原容器元素的空间不再有效,从而使得指向原空间的迭代器失效。
2.由于删除元素使得某些元素次序发生变化使得原本指向某元素的迭代器不再指向希望指向的元素。

在STL里,我们不能以指针来看待迭代器,指针是与内存绑定的,而迭代器是与容器里的元素绑定的,删除了之后,该迭代器就失效了,在对其重新赋值之前,不能再访问此迭代器。

关于容器的迭代器失效的问题,C++ Primier用了一小节作了总结,中文翻译如下:

  (1)增加元素到容器后
  对于vector和string,如果容器内存被重新分配,iterators,pointers,references失效;如果没有重新分配,那么插入点之前的iterator有效,插入点之后的iterator失效;
  对于deque,如果插入点位于除front和back的其它位置,iterators,pointers,references失效;当我们插入元素到front和back时,deque的迭代器失效,但reference和pointers有效;
  对于list和forward_list,所有的iterator,pointer和refercnce有效。
  (2)从容器中移除元素后
  对于vector和string,插入点之前的iterators,pointers,references有效;off-the-end迭代器总是失效的;
  对于deque,如果插入点位于除front和back的其它位置,iterators,pointers,references失效;当我们插入元素到front和back时,off-the-end失效,其他的iterators,pointers,references有效;
  对于list和forward_list,所有的iterator,pointer和refercnce有效。
  (3)在循环中refresh迭代器
  当处理vector,string,deque时,当在一个循环中可能增加或移除元素时,要考虑到迭代器可能会失效的问题。我们一定要refresh迭代器。

2.值得注意的是,std::list是环形的双链表结构,不会因为通过迭代器对容器进行插入或删除而使迭代器失效
所以在此哈希表可以这样实现:

unordered_map<int, list<T>::iterator> cacheMap; 

std::list中,element的移动可以用splice()来实现:

c1.splice(c1.beg,c2) 将c2连接在c1的beg位置,释放c2,PS:释放二字需要斟酌,因为STL做的只是变更了指针的指向,不存在对节点的创建和释放

list<int> a1{1,2,3},a2{4,5,6};
a1.splice(a1.begin(), a2);
list<int>::iterator it;
cout << "a1.merge(a2):";
for(it = a1.begin();it!=a1.end();it++)
{
    cout << *it << " ";
}
cout << endl;

c1.splice(c1.beg,c2,c2.beg) 将c2的beg位置的元素连接到c1的beg位置,并且在c2中释放掉beg位置的元素

list<int> a1{1,2,3},a2{4,5,6};
a1.splice(a1.begin(), a2,++a2.begin());
list<int>::iterator it;
cout << "a1.merge(a2):";
for(it = a1.begin();it!=a1.end();it++)
{
    cout << *it << " ";
}
cout << endl;
return 0;

c1.splice(c1.beg,c2,c2.beg,c2.end) 将c2的[beg,end)位置的元素连接到c1的beg位置并且释放c2的[beg,end)位置的元素

list<int> a1{1,2,3},a2{4,5,6};
a1.splice(a1.begin(),a2,a2.begin(),a2.end());
list<int>::iterator it;
cout << "a1.merge(a2):";
for(it = a1.begin();it!=a1.end();it++)
{
   cout << *it << " ";
}
cout << endl;
return 0;

具体实现:

struct CacheNode
{
    int key;
    int value;
    CacheNode(int k, int v):key(k), value(v){}
};

class LRUCache{
public:
    LRUCache(int capacity) {
        size = capacity;
    }

    int get(int key) {
        if(cacheMap.find(key) == cacheMap.end())
            return -1;
        else
        {
            //把当前访问的节点移到链表头部
            cacheList.splice(cacheList.begin(), cacheList, cacheMap[key]);
            //cacheMap[key] = cacheList.begin();//地址的更新只有在新加入元素时需要处理
            return cacheMap[key]->value;
        }

    }

    void set(int key, int value) {
        if(cacheMap.find(key) == cacheMap.end())
        {
            if(cacheList.size() == size)
            {//删除链表尾部节点(最少访问的节点)
                cacheMap.erase(cacheList.back().key);
                cacheList.pop_back();
            }
            //插入新节点到链表头部,并且更新map中增加该节点
            cacheList.push_front(CacheNode(key, value));
            cacheMap[key] = cacheList.begin();
        }
        else
        {//更新节点的值,把当前访问的节点移到链表头部
            cacheMap[key]->value = value;
            cacheList.splice(cacheList.begin(), cacheList, cacheMap[key]);
            //cacheMap[key] = cacheList.begin();//地址的更新只有在新加入元素时需要处理
        }

    }
private:
    list<CacheNode> cacheList;
    unordered_map<int, list<CacheNode>::iterator>cacheMap;
    int size;
};

参考博客:http://blog.youkuaiyun.com/codercong/article/details/52065130
http://www.cnblogs.com/scandy-yuan/archive/2013/01/08/2851324.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值