经典数据结构-哈希链表-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();
    }
};
相关推荐
宵时待雨10 小时前
优选算法专题9:哈希表
数据结构·算法·散列表
txzrxz10 小时前
最短路问题——Dijkstra 算法
数据结构·c++·算法·最短路·优先队列
noipp11 小时前
推荐题目:洛谷 P6231 [JSOI2013] 公交系统
c语言·数据结构·c++·算法·游戏·洛谷·luogu
jinyishu_16 小时前
哈希表原理与开放定址法C++实现
c++·哈希算法·散列表
青山木17 小时前
Hot 100 ---腐烂的橘子
java·数据结构·后端·算法·leetcode·广度优先
未来之窗软件服务17 小时前
计算机考试-快速排序—东方仙盟
数据结构·算法·排序算法·仙盟创梦ide·东方仙盟
小龙报17 小时前
【优选算法】1. 水果成蓝 2.找到字符串中所有字母的异位词
java·c语言·数据结构·数据库·c++·redis·算法
wWYy.17 小时前
数据结构:跳表
数据结构
变量未定义~18 小时前
虚拟节点-星石传送阵(4星)、强连通分量
数据结构·算法
电子云与长程纠缠20 小时前
UE中使用TGuardValue与TInlineComponentArray数据结构
开发语言·数据结构·学习·ue5·游戏引擎