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     }

posted @ 2019-10-25 20:57  羽园原华  阅读(122)  评论(0编辑  收藏  举报