Ref
哈希链表
概述
1、显然 cache 中的元素必须有时序,以区分最近使用的和久未使用的数据,当容量满了之后要删除最久未使用的那个元素腾位置。
2、我们要在 cache 中快速找某个 key 是否已存在并得到对应的 val;
3、每次访问 cache 中的某个 key,需要将这个元素变为最近使用的,也就是说 cache 要支持在任意位置快速插入和删除元素。
那么,什么数据结构同时符合上述条件呢?哈希表查找快,但是数据无固定顺序;链表有顺序之分,插入删除快,但是查找慢。所以结合一下,形成一种新的数据结构:哈希链表 LinkedHashMap。
LRU缓存机制
完全手撸
我们手撸出双向链表+哈希表->哈希链表,适合面试手撕:
更具体的,构建双向链表,然后尾部作为最近使用的。使用尾插法,然后让哈希表可以映射到链表的key值

这样,get方法只需要借助makeRecently方法把查询的key提升到最近使用的位置;put方法,如果有对应的key就直接删除节点deleteKey,并且重新尾插addRecently。如果不在,先判断cap有没有达到cache(也就是我们实现的双向链表)的容量,有的话使用removeLastRecently,最后addRecently
可见,本质上哈希链表就是借助哈希表给予了双向链表随机查找和修改某个key的能力,这样我们就可以实现LRU的get方法,并且也可以维持一个缓存cache来
cpp
class Node{
public:
int key, val;
Node *next, *prev;
Node(int k, int v) : key(k), val(v), next(nullptr), prev(nullptr) {}
};
class DoubleList{
private:
Node* head;
Node* tail;
int size;
public:
DoubleList(){
head = new Node(0, 0);
tail = new Node(0, 0);
head->next = tail;
tail->prev = head;
size = 0;
}
void addLast(Node* x){
x->prev = tail->prev;
x->next = tail;
tail->prev->next = x;
tail->prev = x;
size++;
}
void remove(Node* x){
x->prev->next = x->next;
x->next->prev = x->prev;
size--;
}
//删除第一个节点并且返回该节点
Node* removeFirst(){
if(head->next == tail){
return nullptr;
}
auto first = head->next;
remove(first);
return first;
}
int getSize(){ return size; }
};
class LRUCache {
private:
unordered_map<int, Node*> mp;
DoubleList cache;
int cap;
public:
LRUCache(int capacity) {
this->cap = capacity;
}
int get(int key) {
if(!mp.count(key)){
return -1;
}
makeRecently(key);
return mp[key]->val;
}
void put(int key, int value) {
if(mp.count(key)){
//如果存在,删除再加入
deleteKey(key);
addRecently(key, value);
return;
}
//不然就额外加入,需要先判断是不是满cap(LRU),不然直接加入
if(cap == cache.getSize()){
removeLast();
}
addRecently(key, value);
}
void makeRecently(int key){
Node* x = mp[key];
cache.remove(x);
cache.addLast(x);
}
void deleteKey(int key){
Node* x = mp[key];
cache.remove(x);
mp.erase(key);
}
void addRecently(int key, int value){
Node* x = new Node(key, value);
cache.addLast(x);
mp[key] = x;
}
void removeLast(){
Node* dn = cache.removeFirst();
mp.erase(dn->key);
}
};
/**
* Your LRUCache object will be instantiated and called as such:
* LRUCache* obj = new LRUCache(capacity);
* int param_1 = obj->get(key);
* obj->put(key,value);
*/
借助STL库
cpp
class LRUCache {
private:
size_t capacity_;
// 链表:元素为 pair<key, value>
std::list<std::pair<int, int>> cacheList_;
// 哈希表:key → 链表中该元素的迭代器
std::unordered_map<int,
typename std::list<std::pair<int, int>>::iterator> cacheMap_;
public:
LRUCache(size_t capacity) : capacity_(capacity) {}
int get(const int& key) {
auto it = cacheMap_.find(key);
if (it == cacheMap_.end()) {
// 未命中
return -1;
}
// 命中:将该节点移动到链表头部,表示最近使用
cacheList_.splice(cacheList_.begin(), cacheList_, it->second);
return it->second->second;
}
void put(const int& key, const int& value) {
auto it = cacheMap_.find(key);
if (it != cacheMap_.end()) {
// 已存在:更新 value + 移动到头部
it->second->second = value;
cacheList_.splice(cacheList_.begin(), cacheList_, it->second);
return;
}
// 新插入:如果满了,淘汰尾部(最久未使用)
if (cacheList_.size() == capacity_) {
auto &node = cacheList_.back();
cacheMap_.erase(node.first);
cacheList_.pop_back();
}
// 插入新节点到头部
cacheList_.emplace_front(key, value);
cacheMap_[key] = cacheList_.begin();
}
bool contains(const int& key) const {
return cacheMap_.find(key) != cacheMap_.end();
}
size_t size() const {
return cacheList_.size();
}
};