经典数据结构-哈希链表-LRU

Ref

  1. 手撸LRU

哈希链表

概述

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();
    }
};
相关推荐
hold?fish:palm7 小时前
24 回文链表
数据结构·c++·链表
qeen8711 小时前
【数据结构】自平衡二叉搜索树各种旋转算法原理解析及AVL树的C++实现
数据结构·c++·算法
冻柠檬飞冰走茶11 小时前
PTA基础编程题目集 7-35有理数均值(C++语言实现)
开发语言·数据结构·c++·算法·均值算法
Nil20813 小时前
leetcode 189轮转数组
数据结构·算法·leetcode
依然鸣14 小时前
PTA团体程序设计天梯赛L1真题讲解L1-085-088
数据结构·c++·经验分享·算法·深度优先·pat考试
Brilliantwxx15 小时前
【算法从零到千】【51-54】逆序对(分治+递归算法)
数据结构·算法·排序算法
小星星闪亮登场15 小时前
2026河南萌新联赛第四场--南阳理工学院
数据结构·算法·贪心算法·动态规划·哈希算法·广度优先
土司大王15 小时前
LeetCode hot100——两数之和
数据结构·算法·leetcode
wabs66616 小时前
关于字符串【力扣344.反转字符串的思考】
数据结构·算法·leetcode
LuminousCPP16 小时前
数据结构基础篇(二):顺序表与链表全方位对比|从内存布局到 CPU 缓存理解底层差异
c语言·数据结构·经验分享·链表·缓存