LeetCode | 146. LRU缓存机制
原题(Medium):
运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put 。
获取数据 get(key) - 如果密钥 (key) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1。
写入数据 put(key, value) - 如果密钥不存在,则写入其数据值。当缓存容量达到上限时,它应该在写入新数据之前删除最近最少使用的数据值,从而为新的数据值留出空间。
思路:双向链表+哈希表
什么是LRU缓存机制,这里就不做介绍了,网上百度就有了。
根据最近最少使用规则,我们可以使用一种双向的数据结构,最近被使用的数据放在数据结构的首端,最近没被使用的数据会根据新数据的加入而逐渐后移,直到空间已经满的时候,从数据结构的尾端淘汰数据。双向链表的List就是个不错的选择,我们可以在更新或添加密钥(key)时使用push_front加入链表首端,使用pop_back淘汰尾端的key。
除此之外,我们知道链表的访问是通过遍历链表完成的,这样效率太低,我们仍需一种能通过密钥(key)快速定位链表位置的数据结构来提升缓存的访问效率,哈希表就能符合我们的需求。
哈希表查找快,但无序,双向链表有序,但查找慢,两者配合相得益彰。
哈希链表实现起来也不难,用链表记录键值对,用哈希表记录键跟键在链表位置,就OK了。如果有疑问说为什么哈希表记录了键,链表又要记录一次键呢?不能链表就只记录值,而键就交给哈希表记录的吗?等等看代码就知道为什么不能了。
我们可以先来看,该缓存类的私有成员,肯定是有一个链表,一个哈希表,跟一个记录容量的变量,记住键值对应该用pair类型来表示:
1 class LRUCache { 2 public: 3 LRUCache(int capacity) : capacity(capacity) { } 4 5 int get(int key) { 6 7 } 8 9 void put(int key, int value) { 10 11 } 12 private: 13 //链表:记录键值对 14 list<pair<int, int>> cache; 15 //哈希表:记录键与键在链表的位置(迭代器) unordered_map为无序的哈希表 16 unordered_map<int, list<pair<int, int>>::iterator> map; 17 int capacity; 18 };
然后来考虑get函数,显然get函数需要做到:获取某键对应的值,如果键存在,根据最近最少使用规则,将其重新放置在链表的首端,并返回值;如果键不存在,返回-1。
1 int get(int key) { 2 //在哈希表内寻找是否有该键对应的值 3 if (map.find(key) == map.end()) return -1; 4 //获取该键对应的键值对,其中map[key]为迭代器,*则是取值,ky为pair类型 5 auto ky = *map[key]; 6 //将其从链表中删除,并重新放回到链表首端 7 cache.erase(map[key]); 8 cache.push_front(ky); 9 //在哈希表中更新该键在链表中的位置 10 map[key] = cache.begin(); 11 return ky.second; 12 }
put函数,首先考虑该键是否已在链表,如果在就进行修改操作,据最近最少使用规则,将其重新放置在链表的首端;如果不在,就进行添加操作,添加前还要考虑是否已经饱和,如果饱和就淘汰掉链表尾端的键值对,没饱和就直接添加在链表首端。
1 void put(int key, int value) { 2 //首先考虑链表内是否有该键 3 if (map.find(key) != map.end()) 4 { 5 //有就删除、修改、置顶一条龙 6 auto ky = *map[key]; //是pair类型,注意*取值 7 cache.erase(map[key]); //删除 8 ky.second = value; //修改 9 cache.push_front(ky); //置顶 10 map[key] = cache.begin(); //在哈希表中更新该键在链表的位置 11 return; //直接退出 12 } 13 //再考虑是否饱和 14 if (cache.size() == capacity) 15 { 16 //饱和,需要删除链表尾端的键值对,同时为了在哈希表中移除该键,先需要从链表中获取尾端的键,这就是为什么链表和哈希表都要记录键 17 map.erase(cache.back().first); 18 cache.pop_back(); 19 } 20 //不饱和,直接在首端添加 21 cache.push_front(make_pair(key, value)); 22 map[key] = cache.begin(); 23 }