分析:为了保持cache的性能,使查找,插入,删除都有较高的性能,我们使用双向链表(std::list)和哈希表(std::unordered_map)作为cache的数据结构,因为:
● 双向链表插入删除效率高(单向链表插入和删除时,还要查找节点的前节点)
● 哈希表保存每个节点的地址,可以基本保证在O(1)时间内查找节点
unordered_map容器比map容器更快地通过键值访问他们的单个元素,虽然unordered_map一般都是比map通过其元素的一个子集范围迭代效率低。哈希map允许使用操作运算符(运算符[])以其键值作为参数直接访问元素。
map是用红黑树实现的,查找效率为O(logn),而unordered_map是通过哈希表实现的,虽然增加空间复杂度,但是查找效率却是O(1),所以unordered_map查找效率更高。
具体实现细节:
● 越靠近链表头部,表示节点上次访问距离现在时间最短,尾部的节点表示最近访问最少
● 查询或者访问节点时,如果节点存在,把该节点交换到链表头部,同时更新hash表中该节点的地址
● 插入节点时,如果cache的size达到了上限,则删除尾部节点,同时要在hash表中删除对应的项。新节点都插入链表头部。
实现代码:
class LRUCache{
private:
struct CacheNode
{
int key;
int value;
CacheNode(int k, int v):key(k), value(v){}//结构体的构造函数
};
list<CacheNode> cacheList;//用于LRU
unordered_map<int, list<CacheNode>::iterator> cacheMap;//存储地址,快速访问元素
int size;
public:
LRUCache(int capacity) {
size = capacity;
}
int get(int key) {
if(cacheMap.find(key) == cacheMap.end())//没有命中
{
return -1;
}
//命中
list<CacheNode>::iterator it = cacheMap[key];//获取命中节点的地址
cacheList.erase(it); //先删除命中的节点
//创建一个新节点(代替命中节点),将其放到链表头部
cacheList.push_front(CacheNode(key,it->value)); //将命中的节点放到链表头部
cacheMap[key] = cacheList.begin(); //更新该key的地址
return cacheMap[key]->value;
}
void set(int key, int value) {
if(cacheMap.find(key) == cacheMap.end())//没有命中
{
if(cacheList.size() == size)//超过cache的长度后,把链表尾部节点删除(最少访问的节点)
{
cacheMap.erase(cacheList.back().key);
cacheList.pop_back();
}
//插入新节点到链表头部,并将新节点的地址插入到map中
//创建一个新节点,将其放到链表头部
cacheList.push_front(CacheNode(key,value)); //将新的节点放到链表头部
cacheMap[key] = cacheList.begin();
}
else //命中
{
list<CacheNode>::iterator it = cacheMap[key];//获取命中节点的地址
cacheList.erase(it); //先删除命中的节点
//创建一个新节点(代替命中节点,注意value用传进来的参数)
cacheList.push_front(CacheNode(key,value)); //将命中的节点放到链表头部
cacheMap[key] = cacheList.begin(); //更新该key的地址
}
}
};