题目:
设计和实现一个 LRU (最近起码应用) 缓存机制 。
LRUCache(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存。
int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1。
void put(int key, int value) 如果关键字曾经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字-值」。当缓存容量达到下限时,它应该在写入新数据之前删除最久未应用的数据值,从而为新的数据值留出空间。
是否能够在 O(1) 工夫复杂度内实现这两种操作?
解决办法:
哈希表 + 双向链表
1、双向链表:节点凑近头部是最近应用的,凑近尾部是最久未应用的,在双向链表的头部增加节点、在双向链表的尾部删除节点的复杂度也为 O(1)。
2、哈希表:通过缓存数据的键映射到其在双向链表中的地位(定位),查找节点工夫复杂度:o(1)。
3、为了在链表操作过程中更少的判断头尾节点是否为null,采纳哨兵机制,头尾都增加哨兵(与传统双向链表性能貌似无很大差异,只是判断nullptr较多容易出错)。
源码剖析:
//节点定义struct DLinkedNode{ int key, value; //节点在hash表里的键值和值 DLinkedNode* prev; //此节点的前驱节点 DLinkedNode* next; //此节点的后继节点 DLinkedNode() :key(0), value(0), prev(nullptr), next(nullptr) {} //构造函数,赋值key和value DLinkedNode(int _key, int _value) :key(_key), value(_value), prev(nullptr), next(nullptr) {} //带参构造函数(重载)}; //LRU 缓存类class LRUCache {private: unordered_map<int, DLinkedNode*> cache; //哈希映射表 DLinkedNode* head; //双向链表头结点 DLinkedNode* tail; //双向链表尾结点 int size; //以后节点个数 int capacity; //LRU缓存最大节点个数(容量) public: LRUCache(int _capacity) :capacity(_capacity), size(0){ head = new DLinkedNode(); //哨兵节点,造成环形链表 tail = new DLinkedNode(); head->next = tail; tail->prev = head; } //获取数据 int get(int key){ //判断是否缓存命中 if (!cache.count(key)){ return -1; } //因为最近拜访了,所以移到链表最前端 DLinkedNode* node = cache[key]; moveToHead(node); return node->value; } //插入数据 void put(int key, int value){ //如果缓存未命中,则创立新的节点,插入哈希,移到链表头部(最近拜访数据) //缓存区满的话,要移除链表尾部节点,因为是最久未拜访的数据 if (!cache.count(key)){ DLinkedNode* node = new DLinkedNode(key, value); cache[key] = node; addToHead(node); ++size; if (size > capacity){ DLinkedNode* removed = removeTail(); cache.erase(removed->key); delete removed; --size; } } else{ //取到此节点 DLinkedNode* node = cache[key]; //赋值新值 node->value = value; //移到链表头部,变为最近拜访的数据 moveToHead(node); } } private: void addToHead(DLinkedNode* node){ node->prev = head; node->next = head->next; head->next->prev = node; head->next = node; } void removeNode(DLinkedNode* node){ node->prev->next = node->next; node->next->prev = node->prev; } //挪动节点到头部 void moveToHead(DLinkedNode* node){ //移除掉此节点 removeNode(node); //最近拜访数据放到头部 addToHead(node); } //移除尾部节点 DLinkedNode* removeTail(){ DLinkedNode* node = tail->prev; removeNode(node); return node; }}; /*测试数据:#include<iostream>#include<unordered_map>using namespace std; int main(){ LRUCache* lRUCache = new LRUCache(2); lRUCache->put(1, 1); // 缓存是 {1=1} lRUCache->put(2, 2); // 缓存是 {1=1, 2=2} lRUCache->get(1); // 返回 1 lRUCache->put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3} lRUCache->get(2); // 返回 -1 (未找到) lRUCache->put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3} lRUCache->get(1); // 返回 -1 (未找到) lRUCache->get(3); // 返回 3 lRUCache->get(4); // 返回 4 system("pause"); return 0;}*/