方法:哈希表+双向链表
- 关键字:key,value对
- 模式识别:一旦出现键值对,就要想到哈希表
- 改变数据的访问时间:1 需要能够随机访问,2 需要能把该数据插到头部或者尾部
测试:
- 测试插入,包括溢出情况
- 测试更新,检查是否正确更新
- 测试读取,检测能否正确读取
hashmap+双向链表,复杂度分析:
- 时间复杂度:O1,哈希表定位O1,链表操作O1
- 空间复杂度:Oc
代码:
struct DLinkedNode {
int key, value;
DLinkedNode* prev;
DLinkedNode* next;
DLinkedNode(): key(0), value(0), prev(nullptr), next(nullptr) {}
DLinkedNode(int _key, int _value): key(_key), value(_value), prev(nullptr), next(nullptr) {}
};
class LRUCache {
private:
unordered_map<int, DLinkedNode*> cache;
DLinkedNode* head;
DLinkedNode* tail;
int size;
int capacity;
public:
LRUCache(int _capacity): capacity(_capacity), size(0) { 在初始化代码中的数据结构同时,初始化capacity和size两个参数
// 使用伪头部和伪尾部节点
head = new DLinkedNode(); 创建头部节点
tail = new DLinkedNode(); 创建尾部节点
head->next = tail; 头部-》尾部
tail->prev = head; 头部《-尾部
}
int get(int key) {
if (!cache.count(key)) { 如果没有则返回-1
return -1;
}
// 如果 key 存在,先通过哈希表定位,再移到头部
DLinkedNode* node = cache[key]; 通过哈希表定位再对节点进行操作
moveToHead(node); 这里将头部表示为最经常使用的元素
return node->value;
}
void put(int key, int value) {
if (!cache.count(key)) {
// 如果 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 {
// 如果 key 存在,先通过哈希表定位,再修改 value,并移到头部
DLinkedNode* node = cache[key];
node->value = value;
moveToHead(node);
}
}
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;
}
};