LRU cache是leetcode上的一道题。
LRU就是Least Recently Used,最近最少使用算法。算法的思想就是
“在前面使用频繁的页面很可能在以后频繁使用。反过来说,已经很久没有使用的页面很可能在未来较长的一段时间内不会被用到”
给定两个操作
1. int get(int key)
,如果给定的key在cache中,返回相应的值,不在则返回-1。
2. void set(int key, int value)
,设置cache中key对应的value,如果不存在,则新增一项。如果cache已满,则使用LRU算法老化。
Cache需要满足的需求:
1. 快速查找,最好是
O(1)
的时间复杂度
2. 某个老化算法,本例中为LRU算法。
3. 老化算法的时间复杂度最好也是
O(1)
的
LRU一般用hash表实现,给每个cache项加上一个命中次数,当cache满的时候,对cache进行一次扫描,找出命中次数最少的一项并删掉。
如果这么实现,查找为
O(1)
,老化为
O(n)
。
另一种实现是利用hash表和链表实现。具体操作如下
- int get(int key)
在hash表中查找key对应的value,并在list中找到对应的节点,将其移动到链表头部。 - void set(int key, int value)
在hash表中更新key对应的value,如果hash表中查不到,则在hash表和链表中都新增一项,新增的一项放在链表头部。如果cache已满,则从list尾部删除一项,并将对应项从hash表中删除。
有两个关键点:
1. 通过hash表要能快速的定位cache在list中的位置,然后移动到链表首部
2. 通过list能快速的定位cache在hash表中的位置,然后将其移除。
如果使用C语言实现链表和hash表,可以很方便的使用指针来定位。
但是已经有了STL,不用白不用。
使用STL的unordered_map和list实现如下。
声明如下
struct cache{
int key;
int value;
};
//LRUCache
template<class key, class value>
class LRUCache{
private:
list<cache*> lru_list;//链表
typedef list<cache*>::iterator location
unordered_map<key, location> lru_hash; //hash表
//...
}
对于第一点,hash表中存放了cache在list中的迭代器,通过list的erase和push_front来使其移动到链表头部,并且使用hash[key] = list.begin()来更新hash中迭代器的值。
大家都知道迭代器会有失效的问题,但是对于list来说,增加节点原有迭代器都不会失效,删除节点也只有被删除的迭代器失效。
因此,在本例中,可以使用hash表来存放list的迭代器。
对于第二点,先在list中移除cache,再通过key来移除hash中的cache。
以上两个过程时间复杂度均为 O(1) 。
以下是完整AC代码。
class cache{
public:
int key;
int value;
cache(int k,int v):key(k),value(v){}
};
class LRUCache{
private:
list<cache*> lru_list;
typedef typename list<cache*>::iterator location;
unordered_map<int, location> lru_hash;
int max_size;
public:
LRUCache(int size):lru_list(),lru_hash(),max_size(size){}
location __get(int k){
auto it = lru_hash.find(k);
if(it != lru_hash.end()){
location loc = it->second;
lru_list.push_front(*loc);
lru_list.erase(loc);
lru_hash[k] = lru_list.begin();
return loc;
}
return lru_list.end();
}
int get(int k){
location loc = __get(k);
if(loc != lru_list.end()){
return (*loc)->value;
}
return -1;
}
void set(int k, int v){
auto it = lru_hash.find(k);
if(it != lru_hash.end()){
location loc = it->second;
(*loc)->value = v;
lru_list.push_front(*loc);
lru_list.erase(loc);
lru_hash[k] = lru_list.begin();
}else{
if((int)lru_hash.size() >= max_size){
cache* tmp = lru_list.back();
lru_list.pop_back();
lru_hash.erase(tmp->key);
delete tmp;
}
cache* c = new cache(k, v);
lru_list.push_front(c);
lru_hash.insert(make_pair(c->key, lru_list.begin()));
}
}
};