LRU缓存机制(Python and C++解法)

题目:

运用你所掌握的数据结构,设计和实现一个  LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put 。

获取数据 get(key) - 如果关键字 (key) 存在于缓存中,则获取关键字的值(总是正数),否则返回 -1。
写入数据 put(key, value) - 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字/值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。

示例:

LRUCache cache = new LRUCache( 2 /* 缓存容量 */ );

cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // 返回 1
cache.put(3, 3); // 该操作会使得关键字 2 作废
cache.get(2); // 返回 -1 (未找到)
cache.put(4, 4); // 该操作会使得关键字 1 作废
cache.get(1); // 返回 -1 (未找到)
cache.get(3); // 返回 3
cache.get(4); // 返回 4

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/lru-cache

思路:

设计的必要条件:
  1.缓存中的元素必须有时序,以区分最近使用的和久未使用的数据,当容量满了之后要删除最久未使用的那个元素,腾出位置。
  2.要在缓存中快速找某个key是否已存在并得到对应的value;
  3.每次访问缓存中的某个key,需要将这个元素变为最近使用的,也就是说缓存要支持在任意位置快速插入和删除元素。
设计:
  哈希表查找快,但是数据无固定顺序;双向链表有顺序之分,插入删除快,但是查找慢。所以结合形成一种新的数据结构:哈希链表。LRU缓存算法的核心数据结构就是哈希链表,双向链表和哈希表的结合体。

  每次默认从链表头部添加元素,那么越靠头部的元素就是最近使用的,越靠尾部的元素就是最久未使用的。
  对于某一个key,可以通过哈希表快速定位到链表中的节点,从而取得对应value。
  双向链表可以获得前驱指针,支持在任意位置快速插入和删除,但是链表无法按照索引快速访问某一个位置的元素,而借助哈希表,可以通过key快速映射到任意一个链表节点,然后进行插入和删除。
  哈希表中已经存储key,链表中还要存储key的原因:当缓存已满,不仅要删除链表节点,还要把哈希表中映射到该节点的key同时删除,而这个key只能由链表节点得到,如果链表节点中只存储value,那么就无法得知key,就无法删除哈希表中的key。

Python解法:

 1 class LRUCache:
 2 
 3     def __init__(self, capacity: int):
 4         self.dict = collections.OrderedDict()  # 有序字典
 5         self.remain = capacity
 6 
 7     def get(self, key: int) -> int:
 8         if key not in self.dict:    return -1
 9         v = self.dict.pop(key)  # 取出该值并删除它
10         self.dict[key] = v  # 将其重新放回缓存,且处于顶部(由于是有序字典)
11         return v
12 
13     def put(self, key: int, value: int) -> None:
14         if key in self.dict:  # 如果key存在,则应先将其value删除再更新为新值
15             self.dict.pop(key)  
16         else:  # 如果key不存在
17             # 缓存未满,放入新数值,容量减一
18             if self.remain > 0: self.remain -= 1  
19             # 缓存已满,放入新数值,popitem返回并删除字典中的最后一对键和值
20             # Last=False,以队列方式弹出键值对,Last=True,以堆栈方式弹出键值对
21             else:   self.dict.popitem(last = False)
22         self.dict[key] = value  # 写入新数据
23 
24 # Your LRUCache object will be instantiated and called as such:
25 # obj = LRUCache(capacity)
26 # param_1 = obj.get(key)
27 # obj.put(key,value)

C++解法:

 1 struct DLinkedNode {
 2     int key, value;  // 双向链表的节点需要保存两个值
 3     DLinkedNode *prev;
 4     DLinkedNode *next;
 5     /*===========================================================
 6     构造函数后面的冒号起分割作用,是类给成员变量赋值的方法,初始化列表,
 7     更适用于成员变量是常量的const型
 8     ===========================================================*/
 9     DLinkedNode(): key(0),value(0),prev(NULL),next(NULL) {}  // 默认初始化
10     DLinkedNode(int _key, int _value): key(_key),value(_value),prev(NULL),next(NULL) {}
11 };
12 
13 class LRUCache {
14 private:
15     unordered_map<int, DLinkedNode*> cache;  // 创建哈希表
16     DLinkedNode *head;
17     DLinkedNode *tail;
18     int size;  // 缓存元素数量
19     int capacity;
20 
21 public:
22     LRUCache(int _capacity):capacity(_capacity), size(0) {  // 构造函数
23         head = new DLinkedNode();  // 哨兵头节点
24         tail = new DLinkedNode();  // 哨兵尾结点
25         head -> next = tail;
26         tail -> prev = head;
27     }
28     void addToHead(DLinkedNode *node) {  // 元素添加至缓存头部
29         node -> prev = head;
30         node -> next = head -> next;
31         head -> next -> prev = node;
32         head -> next = node;
33     }
34     void removeNode(DLinkedNode *node) {  // 删除缓存元素
35         node -> prev -> next = node -> next;
36         node -> next -> prev = node -> prev;
37     }
38     void moveToHead(DLinkedNode *node) {  // 元素移至缓存头部
39         removeNode(node);
40         addToHead(node);
41     }
42     DLinkedNode *removeTail() {
43         DLinkedNode *delNode = tail -> prev;  // 要被删除的链表尾节点
44         removeNode(delNode);
45         return delNode;  // 由于还需要从哈希表中删除,故返回该节点
46     }
47 
48 
49     int get(int key) {
50         if(!cache.count(key))  // 如果要获取的元素不在缓存中
51             return -1;
52         else {
53             DLinkedNode *getNode = cache[key];  // 通过哈希表定位该key的链表节点
54             moveToHead(getNode);  // 将访问的节点移至缓存头部
55             return getNode -> value;
56         }
57     }
58     
59     void put(int key, int value) {
60         if(!cache.count(key)) {  // 如果要添加的元素不在缓存中
61             DLinkedNode *newNode = new DLinkedNode(key, value);  // 创建一个新节点
62             size++;
63             if(size > capacity) {  // 如果超出容量
64                 DLinkedNode *removedNode = removeTail();  // 从链表中删除尾节点
65                 cache.erase(removedNode -> key);  // 从哈希表中删除对应的项
66                 delete removedNode;  // 防止内存泄漏
67                 size--;
68             }
69             cache[key] = newNode;  // 新节点添加进哈希表中
70             addToHead(newNode);  // 添加至链表头部,由于同一key,故可通过哈希表定位该节点  
71         }
72         else {
73             DLinkedNode *yetNode = cache[key];  // 定位到该节点
74             yetNode -> value = value;  // 跟新value
75             moveToHead(yetNode);  // 将访问的元素移至缓存头部
76         }
77     }
78 };
79 
80 /**
81  * Your LRUCache object will be instantiated and called as such:
82  * LRUCache* obj = new LRUCache(capacity);
83  * int param_1 = obj->get(key);
84  * obj->put(key,value);
85  */
posted @ 2020-07-28 16:24  孔子?孟子?小柱子!  阅读(451)  评论(0编辑  收藏  举报