[LeetCode]LRU Cache
Posted 乘风有时
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[LeetCode]LRU Cache相关的知识,希望对你有一定的参考价值。
题目:LRU Cache
操作系统中页面置换算法中有LRU算法(最近最久未用算法),其算法原理如下:
每个页面调入内存时,会有一个记录当前页面距离最近一次访问的时间间隔。
当每次访问页面时,如果页面已经在内存中,就将该页面对应的时间标志清空,其他的标志加一;
如果当前页面不在内存中且内存中没有空闲的大小时就调用页面置换算法,此时,会查找所有的页面,找到上面的标志中最大的页面,将其调出内存。
如果内存可以存放4个页面,以下面的过程调用页面,则其置换过程如下:
调用页面的顺序:4,7,0,7,1,0,1,2,1,2,6
4
7 4
0 7 4
7 0 4
1 7 0 4
0 1 7 4
1 0 7 4
2 1 0 7
1 2 0 7
2 1 0 7
6 2 1 0
上面中页面从左到右依次变得不活跃,每次要替换时,优先置换最右边的页面;
这个题目是用LRU算法设计一个缓存,过程与上面相同。
要求:每个方法访问都是O(1)的时间复杂度;
LRU算法中访问时要O(1)的时间复杂度且同时要更新缓存块,需要插入删除;则可以考虑hash表和链表结合使用,但是如何结合使用是一个问题。
hash表必定需要key,但是对应的值要存什么呢?开始时,我直接把value存到list中,但是这样list要和unordered_map对应就只能在unordered_map中存list的迭代器;
本来以为这样就可以,但是准备动手的时候,突然想起来插入删除是会让它后面的迭代器失效,这样就不能存迭代器了。
这样考虑发现不能让list存value,因为没有办法绕开迭代器失效的问题,于是就考虑list存key,这样unordered_map就必须存value,于是,我就把unordered_map设计成这样unordered_map<int, pair<int, list<int>::iterator>> mCahche;
最外围的int是key的类型,里面有pair<int, list<int>::iterator>,其中first是value,second是和list对应的迭代器;必须要list来实现活跃度的一个排序。
于是就有下面的实现:
/** Design and implement a data structure for Least Recently Used (LRU) cache. It should support the following operations: get and put. get(key) - Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1. put(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. Follow up: Could you do both operations in O(1) time complexity? Example: LRUCache cache = new LRUCache( 2 ); //2 is capacity cache.put(1, 1); cache.put(2, 2); cache.get(1); // returns 1 cache.put(3, 3); // evicts key 2 cache.get(2); // returns -1 (not found) cache.put(4, 4); // evicts key 1 cache.get(1); // returns -1 (not found) cache.get(3); // returns 3 cache.get(4); // returns 4 **/ class LRUCache { public: LRUCache(int capacity):capacity(capacity) {} int get(int key); void put(int key, int value); private: int capacity; list<int> cache;//需要快速插入删除,保存key //因为list删除后后面的iterator就会失效,这样之后用后面的iterator去访问对应的value就是出现未定义的行为 unordered_map<int, pair<int, list<int>::iterator>> mCahche;//需要快速的查找O(1),保存key与value和cache中key的对应的迭代器 }; int LRUCache::get(int key) { auto it = mCahche.find(key);//通过unordered_map快速查找 if (it == mCahche.end())return -1;//没有找到 int value = it->second.first;//保存value cache.erase(it->second.second);//删除当前节点,it.second.second以及其后面的iterator都会失效 cache.push_front(key);//将它插入到cache头部 it->second.second = cache.begin();//将新的iterator赋给it.second.second return value; } void LRUCache::put(int key, int value) { auto it = mCahche.find(key);//通过unordered_map快速查找 if (it == mCahche.end()){//没有找到 if (cache.size() == capacity){//容量已满 mCahche.erase(cache.back());//先删除map中的键值对 cache.pop_back();//再删除list中的键 } cache.push_front(key);//先在list中添加新的键 mCahche[key] = { value, cache.begin() };//再在map中添加新的键值对 } else{ it->second.first = value;//可能value不同,更新value cache.erase(it->second.second);//删除当前节点,it.second.second以及其后面的iterator都会失效 cache.push_front(key);//将它插入到cache头部 it->second.second = cache.begin();//将新的iterator赋给it.second.second } }
思路:
其实这道题可以不用list,这样直接用指针就不会有迭代器失效的问题了;自己定义一个循环双向链表来代替list,双向是为了方便unordered_map存储的时候不用存它的父节点(方便删除),循环是为了方便找到尾节点;可以直接用循环单链表来表示也没有问题;
还要注意链表重要存key和value,为什么要有key呢?因为置换的时候,还需要删除unordered_map的对应项,此时需要通过key来找到。
struct DoubleListNode {//双向链表 pair<int,int> val; DoubleListNode *front; DoubleListNode *back; DoubleListNode(int key, int value) : val(key,value), front(NULL), back(NULL) {} }; class LRUCacheByDoubleLinkList { public: LRUCacheByDoubleLinkList(int capacity) :capacity(capacity),size(0),cache(NULL) {}//不要忘记size和cache的初始化 int get(int key); void put(int key, int value); private: int capacity;//最大容量 int size;//cache实际长度 //循环双向链表,方便插入删除,和找到链尾 DoubleListNode* cache;//需要快速插入删除,保存value unordered_map<int, DoubleListNode *> mCahche;//需要快速的查找O(1),保存key和value的节点 }; int LRUCacheByDoubleLinkList::get(int key) { auto it = mCahche.find(key);//通过unordered_map快速查找 if (it == mCahche.end())return -1;//没有找到 if (it->second == cache)return cache->val.second;//如果是链首 //删除当前链表的节点 DoubleListNode* pre = it->second->front; pre->back = it->second->back; it->second->back->front = pre; //将当前节点插入到链首 it->second->back = cache; it->second->front = cache->front; cache->front->back = it->second;//别忘了更新尾节点的前向指针 cache->front = it->second; cache = it->second; return it->second->val.second; } void LRUCacheByDoubleLinkList::put(int key, int value) { auto it = mCahche.find(key);//通过unordered_map快速查找 if (it == mCahche.end()){//没有找到 if (size == capacity){//容量已满 cache = cache->front;//头结点前向移动一步 mCahche.erase(cache->val.first);//先删除map中的键值对 cache->val.first = key; cache->val.second = value; mCahche[key] = cache;//再在map中添加新的键值对 return; } //容量未满 DoubleListNode* p = new DoubleListNode(key, value); ++size; if (cache){//链表不空 p->back = cache; p->front = cache->front; cache->front->back = p; cache->front = p; cache = p; mCahche[key] = p;//再在map中添加新的键值对 } else{//链表为空 p->back = p; p->front = p; cache = p; mCahche[key] = p;//再在map中添加新的键值对 } } else{ it->second->val.second = value;//可能value不同,更新value if (it->second == cache)return;//如果是链首 //删除当前链表的节点 DoubleListNode* pre = it->second->front; pre->back = it->second->back; it->second->back->front = pre; //将当前节点插入到链首 it->second->back = cache; it->second->front = cache->front; cache->front->back = it->second;//别忘了更新尾节点的前向指针 cache->front = it->second; cache = it->second; } }
以上是关于[LeetCode]LRU Cache的主要内容,如果未能解决你的问题,请参考以下文章